Skip to content

fix(listener): cancel the response body when the client has already disconnected - #412

Open
RaphaelFakhri wants to merge 1 commit into
honojs:mainfrom
RaphaelFakhri:fix/cancel-body-after-disconnect
Open

RaphaelFakhri wants to merge 1 commit into
honojs:mainfrom
RaphaelFakhri:fix/cancel-body-after-disconnect

Conversation

@RaphaelFakhri

Copy link
Copy Markdown

Fixes #411

If the client disconnects while an async handler is still running, the Response body that the handler returns afterwards is never cancelled. The stream's cancel() never runs, so the producer behind the body (an upstream fetch, an SSE generator, a database cursor) stays open until the process exits.

writeFromReadableStreamDefaultReader cancels the reader from the close and error listeners it attaches to the outgoing response. When the response is already destroyed, close has already fired and does not fire again, so the reader is never cancelled. The same happens for a disconnect inside the setTimeout tick in responseViaResponseObject, before the listeners are attached. writeFromReadableStream returns early for a destroyed writable, but it also leaves the stream uncancelled.

This change:

  • Cancels the reader and returns reader.closed in writeFromReadableStreamDefaultReader when the writable is already destroyed.
  • Cancels the stream in the early return of writeFromReadableStream, which covers the cached-response path.

Tests

  • test/listener.test.ts: a handler waits for the request signal to abort, then returns a streaming body. The test asserts that the body's cancel() runs. This is the reproduction from the issue.
  • test/utils.test.ts: one case each for writeFromReadableStream and writeFromReadableStreamDefaultReader with a writable that is already closed.
  • All three fail on main and pass with this change. format, lint and typecheck are clean. utils.test.ts and listener.test.ts pass on Node 20, 22 and 24.
  • Full suite on Node 22: 505 of 506 pass. serve-static.test.ts > Should handle read permission errors gracefully also fails on main on that machine, because the tests run as root and chmod does not stop root from reading the file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Response body returned after client disconnect is never cancelled

1 participant