fix(h2): retire the request that completed, not the head of the queue - #5618
Open
mcollina wants to merge 1 commit into
Open
fix(h2): retire the request that completed, not the head of the queue#5618mcollina wants to merge 1 commit into
mcollina wants to merge 1 commit into
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:
|
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