fix(http): sendWebResponse no longer hangs when the client disconnects during backpressure - #303
Conversation
… backpressure — the 'drain' wait had no other way to resolve, but a closed response never emits 'drain', so every streamed response aborted mid-stream leaked its promise chain, reader, and Response for the rest of the dev/preview session; the wait now races 'close'/'error' and the loop bails once the response is destroyed Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: e0de170 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
ryansolid
left a comment
There was a problem hiding this comment.
Reproduced the hang on next.24 with a raw-socket client that stops reading and then disconnects mid-backpressure: the sendWebResponse promise never settles, exactly as described. On this branch it settles promptly; a slow-consumer happy path (4MB through repeated drain cycles) delivers every byte in both versions, and I also probed the other disconnect shape (parked in reader.read()) — that path already settled correctly via the close handler's reader cancellation, so scoping the fix to the drain wait is complete. Turnkey e2e passes 328/328 with the change. Approving. Non-blocking: this has no automated regression test — understandable since we have no node-only harness for src/http.ts — but the repro is small and browserless, so it would slot into a suite easily; we'll take it as a follow-up.
Problem
sendWebResponse(the node-to-web bridge used by the turnkey dev/preview middlewares) waits out backpressure with:A response whose client has gone away never emits
'drain'— the disconnect surfaces as'close'— and the existing'close'handler only cancels the body reader, which unblocks a pendingread()but not this wait. So every streamed SSR response aborted mid-stream (closed tab, slow mobile client, navigation away) parks the promise chain, the reader, and theResponseobject forever. Over a dev/preview session these leaks accumulate with no visible cause.Fix
The backpressure wait now settles on
'drain','close', or'error'(listeners removed either way), and the write loop bails out early when the response is already destroyed. On disconnect the function returns and the existing'close'handler's reader cancellation completes cleanup; the success path is unchanged.Verification
Two scripts driving a real
node:httpserver throughsendWebResponse:res.writereturnfalse), then destroys the socket while the server awaits drain. Before: thesendWebResponsepromise never settles. After: it settles promptly.🤖 Generated with Claude Code