Repository navigation
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughThe HTTP/2 connection now allows up to eight CONTINUATION frames per header block. A ninth frame terminates the connection with GOAWAY and an ChangesHTTP/2 CONTINUATION limit
Suggested reviewers: Priority: ⬆️ High Merge Risk: ⚪ Minimal · up to
|
|
Status: rebased on main (head 66f2145), no conflicts. CI passed. Ready for review and merge. CI: build #127492 passed. How it was reproduced: the raw-socket script in the PR body. Node fails the request at once with Review of 4f0cadb: three optional notes, all answered on the PR. One is a measured behavior change: a header block that needs more than 8 CONTINUATION frames (over 147,456 bytes at the default frame size) now ends the session, as in node. The PR body has it under Downsides. Related: #36411 (merged) made the same change for the |
There was a problem hiding this comment.
LGTM — small, well-scoped port of nghttp2's CVE-2024-28182 guard.
What was reviewed:
- Counter reset covers both header-block entry points (
handle_headersandhandle_push_promise);handle_continuationis the only consumer. - Error path reuses
local_connection_erroridentically to the existingFLOODED/BAD_CLIENT_MAGICguards;-905message matches nghttp2'snghttp2_strerrorexactly. - Boundary semantics (
> max_continuations→ 8 accepted, 9th errors) match nghttp2 and are covered by both the flood test and the exact-budget acceptance test. max_continuationsispubbut nothing writes it beyond the constructor default — harmless future knob.
Extended reasoning...
Overview
Adds a per-header-block CONTINUATION frame counter to the node:http2 inbound engine (src/runtime/api/bun/h2/connection.rs), capped at nghttp2's NGHTTP2_DEFAULT_MAX_CONTINUATIONS = 8. Exceeding it triggers local_connection_error(InternalError, -905), which sends GOAWAY and surfaces NghttpError with code ERR_HTTP2_ERROR / errno -905 to JS. Supporting changes: the -905 errno constant in wire.rs, its message string in http2.ts, and two tests in node-http2.test.js.
Security risks
This is a DoS-hardening change that fails closed — it only adds a stricter rejection path, so the risk profile is over-rejection of legitimate traffic, not under-protection. The 8-frame × 16 KiB default budget (>128 KiB compressed) sits above the existing byte cap, and the exact-budget acceptance test guards against regressing legitimate large blocks. No auth, crypto, or permission logic is touched.
Level of scrutiny
Medium. The mechanism is a trivial counter + compare, wired through the same local_connection_error machinery already used for FLOODED (-904) and BAD_CLIENT_MAGIC (-903). I verified the counter is reset at every site that sets continuation_stream (HEADERS at ~line 1004 and PUSH_PROMISE at ~line 1714 — the only two), and that finish_header_block clears continuation_stream so the count is per-block. The > 8 comparison gives the 8-accepted/9th-errors boundary the PR verified against node v26.3.0.
Other factors
Tests follow the existing raw-net-server pattern in the same file (http2utils.Frame / HeadersFrame / SettingsFrame), wire error events to resolve, and assert exact code/errno/message. The author ran the full node-http2.test.js (319 pass), node-http2-continuation.test.ts, and h2-conformance.test.ts suites. The parallel gap in fetch()'s h2 client is explicitly called out as tracked separately, which is reasonable given the different error plumbing.
There was a problem hiding this comment.
No bugs found. The change is small and follows nghttp2's CVE-2024-28182 fix exactly (counter + local_connection_error with INTERNAL_ERROR/-905, reset at both HEADERS and PUSH_PROMISE block starts), but it's a security-relevant guard in the HTTP/2 inbound frame parser, so worth a maintainer glance — particularly the decision to leave fetch()'s h2 client (src/http/h2_client/dispatch.rs) for a follow-up.
What was reviewed:
- Counter reset covers both header-block entry points (
handle_headersandhandle_push_promise);finish_header_blockclearscontinuation_streamso the next block always re-arms via one of those paths. - Error path reuses
local_connection_erroridentically to the existingFLOODED/BAD_CLIENT_MAGIC/STREAM_CLOSEDsites; -905 wired intokNghttp2ErrorMessagesmatches nghttp2's string. - Boundary test confirms 8 CONTINUATIONs are accepted and the 9th trips, matching node;
node-http2-continuation.test.tslarge-header cases still pass per the evidence.
Extended reasoning...
Overview
Adds a per-header-block CONTINUATION frame counter to the node:http2 inbound engine (src/runtime/api/bun/h2/connection.rs): two new u32 fields on Connection, reset to 0 when a HEADERS or PUSH_PROMISE opens a header block without END_HEADERS, incremented in handle_continuation, and compared against max_continuations (default 8 = nghttp2's NGHTTP2_DEFAULT_MAX_CONTINUATIONS). Overflow calls local_connection_error(InternalError, -905, ...). wire.rs gains the TOO_MANY_CONTINUATIONS lib-error constant and the default cap; http2.ts maps -905 to the nghttp2 message. Two tests exercise the flood-rejection and the exact 8-frame boundary.
Security risks
This is the security fix — CVE-2024-28182 (CONTINUATION flood). The guard fails closed (connection torn down via GOAWAY, session marked terminated so further input is ignored). I checked that the counter is reset on every path that sets continuation_stream (both handle_headers and handle_push_promise), so a legitimate second header block on the same connection starts from 0. The > max_continuations comparison after a pre-increment gives exactly 8 accepted frames, matching nghttp2/node. No new attack surface is introduced; the only behavior change is earlier teardown on hostile input.
Level of scrutiny
Medium-high: it's a small, mechanical guard that mirrors an upstream reference implementation line-for-line, but it lives in the network-facing HTTP/2 frame dispatch loop and carries a CVE tag. The logic itself is trivially auditable; the reason to defer is that protocol DoS guards are the kind of thing a maintainer should sign off on, and the PR explicitly scopes out the sibling gap in fetch()'s h2 client — that scoping call is reasonable but should be a human's to make.
Other factors
- Error surfacing reuses the existing
local_connection_errorhelper, so GOAWAY emission, session termination, andNghttpErrorconstruction follow the same path asFLOODED(-904) andBAD_CLIENT_MAGIC(-903). max_continuationsispubbut nothing sets it in this PR; that mirrors nghttp2'snghttp2_option_set_max_continuations(which node doesn't expose either), so the fixed default is fine.- The four comment-cop inline notes are marked resolved; the diff shows the trimmed one-line comments.
- robobun evidence shows the full
node-http2.test.js/node-http2-continuation.test.ts/h2-conformance.test.tssuites pass on both debug+ASAN and release.
There was a problem hiding this comment.
LGTM — small, self-contained CONTINUATION-count guard matching nghttp2's CVE-2024-28182 fix.
What was reviewed:
- Counter reset covers both header-block entry points (HEADERS at connection.rs:1002 and PUSH_PROMISE at :1709);
dispatch()already gateshandle_continuationoncontinuation_stream != 0, so the count can't advance outside a block. - Boundary is
> 8(9th frame trips), error path reuseslocal_connection_errorwithInternalErrorGOAWAY + errno -905, same shape as the existing FLOODED/-904 guard; message string matches nghttp2'snghttp2_strerror. - Tests cover both the flood →
ERR_HTTP2_ERROR/-905 case and the exactly-8-frames-accepted boundary; robobun confirms the flood test times out on main and the existing continuation/conformance suites still pass. - The sibling gap in
fetch()'s h2 client is called out and tracked separately.
Extended reasoning...
Overview
Adds a per-header-block CONTINUATION frame counter to the node:http2 inbound engine (src/runtime/api/bun/h2/connection.rs): two new struct fields (continuation_count, max_continuations), reset-to-zero at both block-start sites (HEADERS and PUSH_PROMISE), and a > max_continuations check in handle_continuation that tears down via the existing local_connection_error path. wire.rs gains the TOO_MANY_CONTINUATIONS (-905) lib-error constant and DEFAULT_MAX_CONTINUATIONS = 8; http2.ts gains the -905 → message table entry. Two tests added to node-http2.test.js.
Security risks
This is the security fix — a DoS guard against CVE-2024-28182 (unbounded zero-length CONTINUATION frames pinning the receiver). The change is strictly additive: it can only reject input that was previously accepted, never the reverse, so it fails closed. The over-rejection risk (legitimate large header blocks being dropped) is bounded by the negative test and by the fact that 8 × 16 KiB default frame size ≥ 128 KiB compressed, well above the existing byte cap. No auth/crypto/permission surface is touched.
Level of scrutiny
Medium. The mechanism is a counter with a fixed bound, checked before the byte-cap logic, using the same connection-error helper that FLOODED (-904) and BAD_CLIENT_MAGIC (-903) already route through — so the teardown/GOAWAY/JS-error plumbing is unchanged and already exercised. I traced that continuation_stream is set non-zero at exactly two sites (HEADERS :1001, PUSH_PROMISE :1708) and both reset the counter in the same commit; finish_header_block clears continuation_stream so a stale count between blocks is unreachable. The u32 counter cannot overflow before the guard trips at 9.
Other factors
- robobun evidence shows fails-on-main (timeout) / passes-with-fix in both debug+ASAN and release, plus
node-http2-continuation.test.tsandh2-conformance.test.tsstill green. - Constants and message string are cited to nghttp2 and match Node v26.3.0's observable shape (
errno: -905,code: ERR_HTTP2_ERROR, exact message). - comment-cop flags were addressed in commit 580d0ea and are marked resolved.
max_continuationsispubbut not yet JS-configurable — matches Node, which doesn't exposenghttp2_option_set_max_continuationseither.
alii
left a comment
There was a problem hiding this comment.
Engine change looks right and matches node: 8 CONTINUATIONs accepted, the 9th rejected even when it carries END_HEADERS, counted per block, on both client and server (checked against node 22; main accepts the 9th on both sides). The tests need work before this lands, same gaps as #36411:
- nothing rejects a 9th CONTINUATION, so the two tests only pin the cap to somewhere in 8..63
- neither continuation_count reset is observed by any test, and the PUSH_PROMISE block start is untested
- nothing exercises createServer or checks the GOAWAY code
Minor: max_continuations is a knob nothing sets.
580d0ea to
7acc182
Compare
|
Updated 8:05 AM PT - Oct 17th, 2026
❌ @robobun, your commit 66f2145 has 1 failures in
🧪 To try this PR locally: bunx bun-pr 36410That installs a local version of the PR into your bun-36410 --bun |
alii
left a comment
There was a problem hiding this comment.
Looks ready to merge from this side. The counter now lives on HeaderBlockInFlight, which has a single construction site shared by HEADERS and PUSH_PROMISE, so the per-block reset is structural; the cap is a private const; the four client cases and the two createServer cases pin exactly 8 accepted / 9th rejected (with the message, errno -905 and GOAWAY INTERNAL_ERROR on the server side), and run unmodified against main the accept cases pass while the reject cases fail with main's old behavior (200 delivered, push delivered, request answered); the same six cases pass against a node 22 peer. The two Rust unit tests read correctly but were not executed here, as the body says; nothing on main has touched these files since the merge-base.
|
Thanks for re-checking against node 22. Nothing further planned from my side; Buildkite #93855 is running on b40d1e9. The red Source lints check is the dead-code-escape inventory (NodeHTTPResponse.rs dropped its last escape in #37977 without the inventory following); it passes on this branch's own base and is failing on every PR right now, and a fix branch is already up, so it is unrelated to this diff. |
There was a problem hiding this comment.
I reviewed this PR and didn't find any bugs. Since it's a CVE mitigation in the HTTP/2 protocol engine and alii's earlier round has been addressed but not yet re-reviewed, a human sign-off still makes sense.
What was reviewed:
- Both HEADERS and PUSH_PROMISE reach the single
finish_or_park_header_blockconstruction site, socontinuations: 0is per-block by construction with no reset to forget. - Boundary math: increment-then-
> 8check accepts the 8th and rejects the 9th, matching nghttp2 and the tests on both sides. - The error path (
local_connection_error→ GOAWAY INTERNAL_ERROR + errno -905) follows the same pattern asFLOODED/BAD_CLIENT_MAGIC; the -905 message string matches nghttp2's.
Extended reasoning...
Overview
The PR adds a per-header-block CONTINUATION frame counter to the node:http2 engine to close CVE-2024-28182 (CONTINUATION flood). Production changes are small: a continuations: u32 field on HeaderBlockInFlight, a private MAX_CONTINUATIONS = 8 const, an increment-and-check in handle_continuation, a new lib_error::TOO_MANY_CONTINUATIONS (-905) constant in wire.rs, and the matching message-table entry in src/js/node/http2.ts. The rest is tests: two Rust unit tests, four client-side JS tests in node-http2.test.js, and two server-side JS tests in h2-conformance.test.ts.
Security risks
This is itself a security hardening (DoS mitigation). The change is strictly additive on the receive path — it only introduces a new terminal condition; nothing that previously errored now succeeds. I confirmed both openers (handle_headers at connection.rs:1026 and handle_push_promise at :1767) go through the single finish_or_park_header_block site that initializes continuations: 0, so the budget cannot leak across blocks. The take-mutate-put-back pattern in handle_continuation correctly leaves header_block_in_flight = None on the error return since it was already .take()n. The 8-frame limit matches nghttp2's NGHTTP2_DEFAULT_MAX_CONTINUATIONS, and 8 × 16 KiB max-frame-size comfortably exceeds the existing byte cap, so legitimate large-header traffic is not newly rejected.
Level of scrutiny
High — this is protocol-state-machine code in a network-facing engine, and a CVE fix. The change itself is mechanically simple and follows the exact pattern of the neighboring FLOODED / BAD_CLIENT_MAGIC local-connection-error paths, but per the approval guidelines security-sensitive code paths should get a human sign-off rather than bot-only approval.
Other factors
alii left a substantive review on 2026-08-12 (four comments: pin the exact 9th-frame boundary, cover the per-block reset via response+trailers, cover PUSH_PROMISE, cover createServer + GOAWAY code, drop the unused pub knob for a private const). The rework in 7acc182 + b40d1e9 addresses each point directly and all threads are resolved, but alii has not yet re-reviewed. The comment-cop bot flags are all resolved (b40d1e9 shortened comments). Test coverage is thorough — at-limit / one-past on client, server, and PUSH_PROMISE, plus a per-block-budget test that would catch a session-wide counter — and the PR body reports the JS suites pass locally under bun bd test and fail on main. The two Rust unit tests were only compile-checked (cargo test -p bun_runtime doesn't link in the author's tree), but the JS tests exercise the same engine paths end-to-end.
…36411) ### Problem - A hostile HTTP/2 origin can pin a `fetch()` forever: it answers with `HEADERS` without `END_HEADERS`, then streams zero-length `CONTINUATION` frames. The promise never settles and the client burns CPU parsing 9-byte frame headers (CVE-2024-28182 class; the `node:http2` side is #36410). - `src/http/h2_client/dispatch.rs`, `FT_CONTINUATION` arm: the only bound on a header block is `LOCAL_MAX_HEADER_LIST_SIZE` on accumulated bytes. An empty payload never advances that, so nothing ends the sequence. ### Fix - Add `continuation_count: u8` to `ClientSession`, zeroed at the top of the `FT_HEADERS` arm and incremented in `FT_CONTINUATION`; past `LOCAL_MAX_CONTINUATIONS` (8) the connection fails with `HTTP2EnhanceYourCalm`, which `error_code_for` already maps to `ENHANCE_YOUR_CALM` on the GOAWAY. - The value and behaviour match nghttp2 (`NGHTTP2_DEFAULT_MAX_CONTINUATIONS`) and node 22: 8 frames accepted, the 9th rejected even when it carries `END_HEADERS`. - One reset at the top of `FT_HEADERS` is sufficient because `dispatch_frame` already rejects any non-`CONTINUATION` frame while a block is open, so every `HEADERS` that reaches the arm starts a new block; this covers the tracked-stream and orphan (already-RST'd stream) branches alike. - 8 x 16 KiB (default `MAX_FRAME_SIZE`) is 128 KiB of compressed headers, below the existing 256 KiB byte cap, so the pre-existing `CONTINUATION flood is bounded` test now sees `HTTP2EnhanceYourCalm`; its assertion is updated. - Verified in `test/js/web/fetch/fetch-http2-adversarial.test.ts` (20/20 with the fix; 3 fail on the released binary): - `zero-length CONTINUATION flood is rejected`: the repro, times out without the fix. - `header block split across the full CONTINUATION budget is accepted`: 8 frames deliver a 200. - `9th CONTINUATION is rejected even if it ends the block`: pins the cap at exactly 8 and the comparison at `>`. - `CONTINUATION budget resets for each header block on a stream`: a 103 block and the final 200 block on one stream each use all 8 frames; fails with the reset removed (checked by removing it and rebuilding). - `fetch-http2-client.test.ts` still 62/62. ### Background - An HTTP/2 header block starts in a `HEADERS` frame and, when `END_HEADERS` is not set, continues in `CONTINUATION` frames on the same stream; until one arrives with `END_HEADERS`, no other frame is allowed on the connection (RFC 9113 4.3, 6.10). A response may carry several blocks on one stream: 1xx informational blocks, the final response, and trailers. - HPACK state is connection-wide, so the fetch client also assembles and decodes blocks for streams it has already reset (the "orphan" path in `dispatch.rs`) to keep the decoder in sync; the cap has to apply there too. - `ENHANCE_YOUR_CALM` is the HTTP/2 error code for "peer is generating excessive load"; `HTTP2EnhanceYourCalm` is the `fetch()` error the client already uses for its other flood limits. <!-- robobun:evidence:begin --> --- **no test proof** · iteration 5 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/web/fetch/fetch-http2-adversarial.test.ts <!-- robobun:evidence:end -->
|
@robobun this conflicts with main now, please rebase and get a fresh CI run so it can be merged. |
b40d1e9 to
5d65ca8
Compare
|
Rebased onto main (5d65ca8). The only conflict was in the CONTINUATION describe of h2-conformance.test.ts, where #37961 added a reassembly test in the same spot; both are kept. No changes to the engine diff. Locally on the rebased build: node-http2.test.js 381 pass / 6 skip, h2-conformance + node-http2-continuation + h2-push-refusal-staged 79 pass, source lints pass. Fresh CI run is Buildkite #97258. |
5d65ca8 to
4f0cadb
Compare
|
Rebased on main (e4c638f) as one commit, 4f0cadb. The branch has no conflicts now.
Checked on a debug build of this commit:
On a debug build of main the same tests fail where they must: the 9th CONTINUATION test in |
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Additional findings (outside the current diff — GitHub can't attach inline comments there):
-
🟡
src/runtime/api/bun/h2/connection.rs— Bun-to-Bun sessions that carry a header block over 9 x the peer's max frame size now lose the whole session, including every other multiplexed stream, where the base delivered it. send_header_block at connection.rs:1977-1992 splits enc_buf into as many CONTINUATION frames as needed with no limit, so a block over about 147 KiB at the default 16384 emits 9 or more CONTINUATIONs. The upgraded receiver kills the connection at connection.rs:1175 with GOAWAY(INTERNAL_ERROR). Node never emits this: nghttp2 fails the submit locally as a stream-level error. Fix: bound the outbound block at the sender (reject the submit for the one stream, as nghttp2 does) so a large header list fails that stream instead of letting the peer tear down the session; cover HEADERS, trailers and PUSH_PROMISE emission.Why this was flagged
Trigger: a Bun server (or client) with a peer that raised maxHeaderListSize above about 147 KiB sends a response, request, trailer or PUSH_PROMISE block larger than 9 x remote_settings.max_frame_size (connection.rs:1958). The loop at connection.rs:1977-1992 writes one CONTINUATION per max-frame chunk with no count check, so it emits 9+ CONTINUATIONs. On the base branch a Bun receiver accepted that block when its enforced_max_header_list_size (connection.rs:1192) covered it; after this change the receiver trips MAX_CONTINUATIONS at connection.rs:1175 and sends GOAWAY(INTERNAL_ERROR), closing every other stream on the session. nghttp2 refuses to pack a header block beyond its framebuf limit and fails the submit (NGHTTP2_ERR_HEADER_COMP / frame error on the one stream), so node never puts a peer in this position. Remedy: cap the outbound block (or the CONTINUATION count) in send_header_block and surface a stream error instead of emitting more than MAX_CONTINUATIONS frames.
Verification: The new receive-side guard at src/runtime/api/bun/h2/connection.rs:1174-1184 tears down the whole session on the 9th CONTINUATION of a block. On the base branch the only receive-side bound was the byte cap at connection.rs:1192-1197,
let cap = (self.enforced_max_header_list_size as usize).max(65536). Bun's send side has no matching bound.
The inbound engine bounded a header block under reassembly only by its
byte size, so a peer sending HEADERS without END_HEADERS followed by
zero-length CONTINUATION frames could hold the session in header-block
state indefinitely: nothing ever errored, the request stayed pending, and
the receiver spent a core parsing 9-byte frames. This is the bug nghttp2
fixed as CVE-2024-28182; node errors after the 8th CONTINUATION.
Count CONTINUATION frames on the in-flight block record and fail the
connection once a block exceeds nghttp2's NGHTTP2_DEFAULT_MAX_CONTINUATIONS
(8), the same way node does: GOAWAY(INTERNAL_ERROR) on the wire and an
NghttpError with errno -905 ("Too many CONTINUATION frames following a
HEADER frame") on the session. The counter lives on HeaderBlockInFlight,
so every HEADERS or PUSH_PROMISE that opens a block starts from zero and
the budget is per block, not per session. Applies to clients and servers
alike since they share the engine.
Tests pin the exact boundary on both sides (8 accepted, the 9th rejected
even when it carries END_HEADERS), the per-block reset (response and
trailers each using the full budget on one stream), the PUSH_PROMISE
opener, and the GOAWAY error code a server emits.
4f0cadb to
66f2145
Compare
|
CI on 4f0cadb (build 126527) had two red tests that this PR does not touch: Rebased again on main (017b243) as 66f2145, so that the next run has those two fixes. The rebase had no conflicts, and main did not change a file of this PR. On a debug build of this commit: |
|
Reply to the review note on large header blocks (the finding outside the diff). I measured it. The finding is correct, and the PR body lists it under Downsides. Setup: a server answers with N header fields of 5,000 bytes each. The client sets
(1) With
|
Problem
node:http2clients and servers accept any number of CONTINUATION frames for one header block. A peer that sends HEADERS without END_HEADERS, then empty CONTINUATION frames, holds the session with no error (CVE-2024-28182).handle_continuationinsrc/runtime/api/bun/h2/connection.rslimits a block by its bytes only.Fix
HeaderBlockInFlightcounts the CONTINUATION frames of its block. AboveMAX_CONTINUATIONS(8, the limit node uses) the connection ends.ERR_HTTP2_ERROR, errno -905,Too many CONTINUATION frames following a HEADER frame.test/js/node/http2/node-http2.test.jsandtest/js/node/http2/h2-conformance.test.ts(8 frames accepted, the 9th refused). Alsonode-http2-continuation.test.tsandh2-push-refusal-staged.test.ts.Background
HeaderBlockInFlightis the engine's record of an open block. HEADERS and PUSH_PROMISE build it at one site, so each block starts at zero.maxHeaderListSize. It keeps large blocks, but it differs from node and is weaker where the size was raised.Downsides
maxHeaderListSize. Node has the same limit.Connection) and +67 bytes of text in the release binary. Each CONTINUATION frame costs one add and one compare.Notes
Measurements. Release builds of the merge base (017b243) and of this PR (66f2145), and node v26.3.0. A raw TCP peer sends the frames.
The row with 8 CONTINUATION and a PING is a known difference. RFC 9113 (6.2, 6.10) requires PROTOCOL_ERROR for a frame of another type while a block is open, which is what Bun sends. nghttp2 counts the frame before it reads the frame type.
Large header blocks (the first Downsides bullet). A server answers with N header fields of 5,000 bytes each. The client sets
maxHeaderListSize: 1048576. A second stream is open on the same session.maxFrameSize: 1048576(1) With
maxSendHeaderBlockLength: 1048576on the node server. With the default (65,536) the node server refuses both blocks before it sends a frame:frameError,NGHTTP2_FRAME_SIZE_ERROR, and only that stream fails. Bun has no default send limit (#43474 proposes one), so a Bun sender emits these blocks with no option set. The Bun sender fills each frame to the frame size of the peer, which is why a largermaxFrameSizeon a Bun receiver keeps large blocks between two Bun peers. Node splits a block at 16,384 bytes in all cases.Cost numbers.
sizeon the release binaries: text 106,738,870 (merge base) and 106,738,937 (this PR). Data (116,624) and bss (1,823,264) do not change. The stripped file is 106,911,456 bytes in both.size_of::<Connection>()is 448 on the merge base and 456 on this PR (x86_64 Linux).HeaderBlockInFlightgoes from 16 to 20 bytes. OneConnectionexists for each HTTP/2 session.u32.Tests.
test/js/node/http2/node-http2.test.js(client): a response and its trailers on one stream each use exactly 8 CONTINUATION frames and are delivered. A 9th frame on a response block gives errno -905. A PUSH_PROMISE block with 8 frames is delivered, with 9 it gives errno -905. The two 9th-frame tests fail on main, which delivers the 200 and the push.test/js/node/http2/h2-conformance.test.ts(server, raw client againstcreateServer): 8 CONTINUATION frames dispatch the request with no GOAWAY. The 9th gives GOAWAY(INTERNAL_ERROR),sessionErrorwith errno -905, and nostreamevent. The 9th-frame test fails on main.connection.rstest module cover the count for each block (HEADERS) and the PUSH_PROMISE opener. They compile (cargo clippy --all-targets), but they were not executed:cargo test -p bun_runtimedoes not link in this tree. The JS tests drive the same engine paths.node-http2.test.js429 pass and 6 skip,h2-conformance.test.ts86 pass,node-http2-continuation.test.ts9 pass,h2-push-refusal-staged.test.ts1 pass. In one run of three files together, two stream-reset flood tests ofh2-conformance.test.tspassed the 5 s limit of the debug build. This PR does not touch them (test: make the h2 stream-reset flood tests pass on debug builds #44256 is about that).once(srv, "stream"), so a regression there ends on the test timeout and not on an assertion.Related.
fetch().Repro. Node prints the -905 error at once. Bun on main prints
STILL PENDING.History. The first revision kept the count as fields on
Connectionwith a publicmax_continuationssetting, and it tested 64 frames and 8 frames on the client only. After review the count moved intoHeaderBlockInFlight(from #37637), the setting became a private constant, and tests were added for the 9th frame, for the count of each block, for PUSH_PROMISE and for the server side.[review] gate passed · iteration 3 · 5 files touched
fails on main (without fix)
passes on PR (with fix)
diff hotspot
gate history · 3 passed · 0 rejected · iteration 3
evidence per changed file