| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
…completion The HTTP/2 'response' handler (onResponse) only guarded request.aborted before calling request.onResponseStart, while its sibling handlers onEnd and onTrailers also guard request.completed. A 'response' frame delivered after the request has already completed (a stream-teardown race that shows up under load on shared h2 sessions with GOAWAY / refused-stream churn) therefore calls onResponseStart post-completion, tripping its assert(!this.completed). Because it throws on the http2 stream's event tick, it surfaces as an uncaught exception and crashes the process. Add the same request.completed guard the other handlers already use. Signed-off-by: Scott Taylor <scott.c.taylor@mac.com> Assisted-By: devx/26cd7e09-2a13-4e79-9cd0-8191fff4dc7a
There was a problem hiding this comment.
i'm assuming you need a maintainer's approval? But approving in case this just requires anyone's approval
Sorry, something went wrong.
There was a problem hiding this comment.
can you add a test for that?
Sorry, something went wrong.
Regression test for the onResponse completed-guard. Drives the real onResponse handler (via connectH2) against a fake stream — mirroring test/http2-late-data.js — and emits a 'response' event after the request has completed. Without the guard this invokes request.onResponseStart, whose assert(!this.completed) throws on the http2 event tick and crashes the process; with the guard the stream is released and the event ignored. Fails against the pre-fix handler with the exact assert(!this.completed) AssertionError and passes with the fix. Refs: nodejs#5440 Signed-off-by: Scott Taylor <scott.c.taylor@mac.com> Assisted-By: devx/26cd7e09-2a13-4e79-9cd0-8191fff4dc7a
|
Added a regression test in test/http2-response-after-completion.js (63439bbd). As noted, a real post-completion 'response' only arises from Node's internal http2 event-ordering race (a frame buffered before teardown, delivered to a still-live stream after the request completed), so it can't be driven deterministically through a real server. The test instead uses the same white-box harness as test/http2-late-data.js — connectH2 over a FakeSocket/FakeSession/FakeStream with a real Request — and emits a 'response' event on the stream after the request has reached completed, driving the actual onResponse handler. It's a true RED/GREEN test: against the pre-fix handler it fails with the exact assert(!this.completed) AssertionError, and it passes with the request.completed guard. It asserts that onResponse runs (onResponseStarted fires before the guard), that the post-completion onResponseStart is skipped, and that the stream is released (releaseRequestStream removes its listeners). |
Sorry, something went wrong.
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## main #5440 +/- ##
=======================================
Coverage 93.44% 93.45%
=======================================
Files 110 110
Lines 37078 37081 +3
=======================================
+ Hits 34649 34655 +6
+ Misses 2429 2426 -3 ☔ View full report in Codecov by Harness.
|
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Bug
The HTTP/2 client's onResponse handler in lib/dispatcher/client-h2.js calls request.onResponseStart(...) after guarding only request.aborted — but not request.completed:
Request.onResponseStart asserts both invariants:
So if a 'response' event is delivered to the stream after the request has already completed, onResponse invokes onResponseStart post-completion and trips assert(!this.completed). Because the assertion throws on the http2 stream's event tick (not inside any caller's try), it escapes as an uncaught exception and crashes the process.
The sibling handlers already treat a post-completion delivery as expected and guard against it:
onResponse is the only one of the three missing the completed guard — that asymmetry is the bug.
Fix
Add the same request.completed guard the other handlers use:
How it shows up
We hit this in production on undici@7.22.0 (same defect there: the v7 'response' handler guards only request.aborted, and the method was named onHeaders, which also assert(!this.completed)). The crash:
It reproduces under load when many requests are multiplexed over shared, long-lived h2 sessions (HTTP/2 connection coalescing to a CDN edge), where server-initiated GOAWAY / refused-stream churn causes frequent concurrent stream teardowns — i.e. exactly the completion-vs-'response' race this guard handles. On the default per-origin HTTP/1.1 path the client-h2.js code is never exercised, so it only manifests with allowH2: true.
Note on a test
This is a client-side event-ordering race; I couldn't construct a deterministic, non-flaky regression test against a real http2 server without contriving the stream's event emission. The change mirrors the existing onEnd/onTrailers guards exactly. Happy to add a test if a maintainer can point me at the preferred way to drive a post-completion 'response' on the request stream.
This affects main (v8.5.0) and v7.x — flagging in case a backport is wanted.