Repository navigation
fix(h2): retire the request that completed, not the head of the queue - #5618
Merged
Merged
Conversation
Backport of #5410 (and the fast path from #5569). HTTP/2 completes out of order, but every completion site advanced the running index blindly: client[kQueue][client[kRunningIdx]++] = null so whichever request happened to sit at the head was retired instead of the one that actually finished. With two streams in flight, completing the second one clears the first's slot while the second stays in the running window for good: after /second queue=[null, "/second"] runningIdx=1 pendingIdx=2 after /third queue=[null, null, "/third"] runningIdx=2 pendingIdx=3 The still-running /first is gone from the queue and two finished requests are counted as running forever, so kRunning never returns to zero. Since _resume() stops dispatching once kRunning reaches the concurrency limit, a client accumulating these eventually stops sending anything. Port completeRequest() from main: retire the request by identity, keeping the O(1) in-order fast path, and splice it out when it finished out of order. Cleared slots can now appear in the queue, so the paths that walk it skip them, as they do on main. Refs: #5404 Refs: #5410 Refs: #5569 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A49JamgF2TkZHu5h58ChUM Signed-off-by: Matteo Collina <hello@matteocollina.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## v7.x #5618 +/- ##
==========================================
- Coverage 93.17% 93.15% -0.02%
==========================================
Files 112 112
Lines 36751 36802 +51
==========================================
+ Hits 34241 34283 +42
- Misses 2510 2519 +9 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
metcoder95
approved these changes
Jul 30, 2026
Merged
meta-codesync Bot
pushed a commit
to facebook/memlab
that referenced
this pull request
Sep 29, 2026
Summary: Bumps [[ https://github.com/nodejs/undici | undici ]] from 7.29.0 to 7.30.0 in the memlab `website/` workspace. `yarn.lock` change only; it also picks up the 7.29.1 security release. **v7.30.0** - fix: selectively re-enable SIMD for ppc64 ([[ nodejs/undici#5794 | #5794 ]]) - Backport upgrade diagnostics lifecycle fixes ([[ nodejs/undici#5783 | #5783 ]]) - fix: honor backpressure in the decompression interceptor ([[ nodejs/undici#5837 | #5837 ]]) - fix: close rejected HTTP/2 WebSocket streams ([[ nodejs/undici#5876 | #5876 ]]) - test(fetch): make pull-dont-push exceed any socket buffer ([[ nodejs/undici#5889 | #5889 ]]) **v7.29.1 — security fixes** High severity: - [[ GHSA-w293-vg96-wgc3 | GHSA-w293-vg96-wgc3 ]]: `BalancedPool` could drop function-valued connection options, including custom TLS certificate validation callbacks, when cloning its configuration - [[ GHSA-rfgv-xxqx-mfg5 | GHSA-rfgv-xxqx-mfg5 ]]: a WebSocket server selecting a subprotocol when none was requested caused an uncaught `TypeError` that could terminate the process Medium severity: - [[ GHSA-3wwx-pv8p-q78v | GHSA-3wwx-pv8p-q78v ]]: a permessage-deflate payload over the decompression limit could emit an unhandled zlib error and terminate the process - [[ GHSA-rx4f-c7p8-82vq | GHSA-rx4f-c7p8-82vq ]]: an unclean `WebSocketStream` close with a locked writable stream could create an unobserved rejected promise - [[ GHSA-2jfj-6hjv-fm6j | GHSA-2jfj-6hjv-fm6j ]]: shared caches could store and replay responses containing `Set-Cookie`, disclosing one user's cookies to another caller - [[ GHSA-3xpg-4rpp-hhhm | GHSA-3xpg-4rpp-hhhm ]]: the decompression interceptor did not bound decoded output; every stage is now limited to 64 MiB by default, configurable via `maxSize` - [[ GHSA-pmjh-fq2x-6v4x | GHSA-pmjh-fq2x-6v4x ]]: a terminal retry failure after response headers were exposed could orphan the response body and hang consumers Low severity: - [[ GHSA-8436-99hf-9mmv | GHSA-8436-99hf-9mmv ]]: cache interceptors could store and replay responses to unsafe methods such as `POST` or `DELETE` - [[ GHSA-2gqq-gqf2-x968 | GHSA-2gqq-gqf2-x968 ]]: the dump interceptor could treat an oversized chunked response without `Content-Length` as successfully truncated - [[ GHSA-r53p-7pc4-xj5r | GHSA-r53p-7pc4-xj5r ]]: the retry interceptor could concatenate a resumed response with inconsistent framing, enabling response splitting or corruption **v7.29.1 — other changes** - fix(h2): honour `headersTimeout` ([[ nodejs/undici#5604 | #5604 ]]) - fix(h2): keep the connection ref'd while requests are outstanding ([[ nodejs/undici#5605 | #5605 ]]) - fix(h2): retire the request that completed, not the head of the queue ([[ nodejs/undici#5618 | #5618 ]]) - fix(h2): settle a request whose stream is cancelled ([[ nodejs/undici#5607 | #5607 ]]) - fix(h2): handle GOAWAY for CONNECT streams ([[ nodejs/undici#5640 | #5640 ]]) - perf: reduce `EventSourceStream` parser allocations ([[ nodejs/undici#5646 | #5646 ]]) - perf(h1): drop the idle-socket timer floor with a ref'd `setImmediate` ([[ nodejs/undici#5769 | #5769 ]]) - CI only: drop Node.js 26 from the shared-builtin build ([[ nodejs/undici#5592 | #5592 ]]); raise the Windows workflow timeout ([[ nodejs/undici#5621 | #5621 ]]) Full changelog: [[ nodejs/undici@v7.29.0...v7.30.0 | v7.29.0...v7.30.0 ]] Opened by Dependabot. Comment `dependabot rebase` on the GitHub PR to resolve conflicts; do not hand-edit the PR branch. Pull Request resolved: #155 Differential Revision: D122269178 Pulled By: JacksonGL fbshipit-source-id: 56fb568c08248253c483c6b44b1175af4a6b6d0c
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.
Backport of #5410, with the in-order fast path from #5569. Neither is on
v7.x— the h2 queue commits after #4881 never came across.The bug
HTTP/2 completes out of order, but every completion site advanced the running index blindly:
That retires whichever request happens to sit at the head, not the one that actually finished. With two streams in flight (
/firstnever answered,/secondand/thirdserved):The still-running
/firsthas been dropped from the queue, and two finished requests are counted as running for good.kRunningnever returns to zero, and since_resume()stops dispatching oncekRunningreaches the concurrency limit, a client that accumulates these eventually stops sending anything at all.The change
completeRequest()ported from main: retire the request by identity, keeping the O(1) fast path when it is the head, and splice it out when it finished out of order. Cleared slots can now appear in the queue, so the paths that walk it skip them — the sameif (request != null)guards #5410 added toclient.js, which cherry-picked cleanly.Reachability on v7.x
Worth knowing for triage: v7 only dispatches h2 requests concurrently up to
pipelining, which defaults to1, so the out-of-order case needspipelining(or a pool where several requests share a client) to show up. main addedgetMaxConcurrent()to dispatch up tomaxConcurrentStreamsfor h2, which is why this bites much harder there. The ported test therefore setspipelining: 2— without it,/secondand/thirdsimply queue behind/firstand never run.Tests
test/issue-5404.jsbackported from #5410, pluspipelining: 2. Fails on the current branch (queue is[null, null, '/third']instead of['/first']), passes with this change.test/+(http2|h2)*.js: 50 pass. Full unit suite: 1304 pass, 0 fail.A seeded chaos harness against v7 currently aborts within a round or two of starting, reporting requests left in flight with nothing outstanding. With this change it no longer drifts; stacked with #5607 it runs all 25 rounds clean (~200 requests per seed,
stuck=0) on every seed tried.Relationship to the other v7 PRs
Independent of #5604, #5605 and #5607 — different bugs, no overlapping hunks. #5607 is the one that makes the harness above reach
stuck=0; this one is what stops the queue drifting.🤖 Generated with Claude Code
https://claude.ai/code/session_01A49JamgF2TkZHu5h58ChUM