Repository navigation
Conversation
The body pull loop runs decompression after onComplete has already fired, but onComplete removed the fetch controller's 'terminated' listener (onAborted). When zlib fails (e.g. a response declares content-encoding: gzip but the body is not valid gzip), controller termination never reaches the response stream, so text(), json() and stream reads hang forever and the AbortSignal cannot end them. Register the onAborted listener only once per fetch (guarded by the presence of fetchParams.controller.resume) and stop removing it in onComplete. This keeps the listener alive until the fetch settles so a late decompression failure correctly errors the stream, while avoiding the MaxListenersExceededWarning on redirects that the single registration prevents. Fixes nodejs#5999 Backport of nodejs#3754. Refs: nodejs#3754
Deferring the gzip body with a wall-clock setTimeout is flaky and adds a fixed delay. Defer it to a later event-loop tick with setImmediate instead, which deterministically splits the headers and body into separate packets without any wall-clock dependency. Refs: nodejs#5999
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This relates to...
Fixes #5999
Rationale
On v6.x (6.7.0 – 6.29.0, the line bundled with Node 22), a response that declares
content-encoding: gzipbut carries a body that is not valid gzip never settlestext(),json()or a stream read when the body arrives in a later packet than the headers. The request'sAbortSignalcannot end it either, so the promise hangs forever.onCompleteremoves the fetch controller'sterminatedlistener (onAborted, added in #2823) as soon as the raw bytes have arrived. Decompression happens later in the body pull loop, so when zlib fails (Z_DATA_ERROR) and the loop callscontroller.terminate(err), nothing errors the response stream.mainfixed this in #3754 by no longer removing the listener inonCompleteand registering it only once per fetch. That change was never backported.Changes
afeb626c(fix aborting Streams #3754) onto v6.x.onAbortedlistener only once per fetch (guarded byfetchParams.controller.resume) and stop removing it inonComplete. Keeping the listener registered means a late decompression failure correctly errors the response stream; registering it once per fetch prevents theMaxListenersExceededWarningon many redirects (redirecting >11 times causes "MaxListenersExceededWarning" #1711) that the previous per-request registration caused.test/fetch/encoding.js. It times out without the fix.test/fetch/issue-1711.js.Bug Fixes
onComplete.Verification
terminated(Z_DATA_ERROR) instead of hanging.test/fetch/issue-1711.js(redirects + abort-stream) passes.test/fetch/*.jswith--expose-gc— all pass except pre-existing environment failures caused by missing dev deps (formdata-node,jsdom) and a Node version--max-http-header-sizedifference, which fail identically on the unpatched branch.standardlint passes on the changed files.Status