Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

WebSocket disconnect never ends a connect() generator waiting on incomingMessages

Open Beginner friendly
#3,261 1 comment 0 reactions 0 assignees View on GitHub

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
Domain
api, backend

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 the close listeners but leaves IterableEventQueue.closed false. It also does not settle the pending next() (resources/IterableEventQueue.ts).
  • iterator.return() on an async generator that is suspended at an await is 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

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from HarperFast/harper

All issues in HarperFast/harper

Similar issues

More JavaScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.