WebSocket disconnect never ends a connect() generator waiting on incomingMessages
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- javascript, node.js
Research direction
Read the WebSocket close handler in server/REST.ts and the queue behavior in resources/IterableEventQueue.ts. Confirm how close() settles a pending next() and preserves close listeners, then run the relevant WebSocket and queue tests. Done when disconnecting closes the incoming message queue and the connect() generator’s cleanup runs.
Written by the indexing model from the issue text.
Description
Summary
When a WebSocket client disconnects, Harper signals close on the incomingMessages queue without closing it. A connect() generator waiting in for await (const message of incomingMessages) therefore never resumes: its finally never runs, and whatever it opened stays open.
This affects the echo pattern that reference/rest/websockets.md documents:
export class Echo extends Resource {
async *connect(incomingMessages) {
for await (let message of incomingMessages) yield message;
}
}
Where
server/REST.ts on main @ c4e8a3493, in the WebSocket close handler:
incomingMessages.emit('close');
if (iterator) iterator.return();
emit('close')runs thecloselisteners but leavesIterableEventQueue.closedfalse. It also does not settle the pendingnext()(resources/IterableEventQueue.ts).iterator.return()on an async generator that is suspended at anawaitis queued until that await settles. Here that never happens, because no further message arrives after the socket closed.
Reproduction
Script run against the built dist/resources/IterableEventQueue.js. It mirrors the two close handler lines above:
const incomingMessages = new IterableEventQueue();
async function* connect(messages) {
try {
for await (const message of messages) yield message;
} finally {
console.log('generator finally ran');
}
}
const iterator = connect(incomingMessages)[Symbol.asyncIterator]();
iterator.next();
incomingMessages.emit('close');
iterator.return(); // never settles; 'finally' never prints
// after 200 ms: return settled = false, queue closed = false
Impact
- No external hold: a generator like the bare echo is unreachable once the socket is gone, so it is garbage-collected. Only its cleanup is skipped.
- External hold: a generator that also holds something external stays alive for the life of the process, once per closed connection. Examples are a table subscription, a timer, or an upstream connection. Nothing is logged.
Proposed fix
Call incomingMessages.close() instead of emit('close'). close() still emits close, so existing listeners keep working. It also ends the pending next(), so the generator's loop exits and the queued return() runs.
A generator that waits on anything other than incomingMessages still needs request.signal, which aborts on disconnect (harper#513). The SSE form of the parked-iterator teardown is harper#1399.
Found while answering a review question on harper#3257.
Related validation
Also track a real-transport oracle for the outgoing drain-close repair in PR #3199: producer cleanup does not directly observe outer REST handler completion. The follow-up comment describes the negative control and the required completion check. Use real modules and sockets under the repository testing rules.
Review-Deferral: 451aea71a9a77983 severity=minor category=test-coverage
🤖 Generated by Claude Code and OpenAI Codex; posted via @kriszyp.
- Dominant language
- JavaScript
- Stars
- 90
- Forks
- 15
- Avg merge
- 1d 23h
- Merged PRs (30d)
- 287
Getting set up
- Ships a Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from HarperFast/harper
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
HarperFast/harper#3330 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
HarperFast/harper#3312 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
HarperFast/harper#3304 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
HarperFast/harper#3299 ·
Maintainers usually reply within 1 day
-
CI: the LMDB unit leg runs no unitTests/server suite, so MQTT durable-session tests never run on LMDBPossibly taken @sudarshan24-10 claimed this 5 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
HarperFast/harper#3275 · 1 comment ·
Maintainers usually reply within 1 day
All issues in HarperFast/harper
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
awesome-dsh-plugin/awesome-dsh-plugin#9184 ·
Maintainers usually reply within 1 day
-
CloudredirectOpen
Difficulty 1/5 Under an hour Newbie friendliness 70/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
NanmiCoder/dsh-agent-teams#277 ·
Maintainers usually reply within 1 day
-
[ordering-review] workshop order graph: 1 confirmed ordering problem, 3 cycles are false positivesOpen
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
githubnext/gh-aw-workshop#4943 ·
Maintainers usually reply within 1 day