Skip to content

node:http2: honor a peer INITIAL_WINDOW_SIZE decrease on client upload flow control - #33602

Closed
robobun wants to merge 5 commits into
mainfrom
farm/609eb5c7/http2-initial-window-decrease
Closed

robobun wants to merge 5 commits into
mainfrom
farm/609eb5c7/http2-initial-window-decrease

Conversation

@robobun

@robobun robobun commented Jul 7, 2026 •

Copy link
Copy Markdown
Collaborator

What

A node:http2 client that is uploading ignores a mid-stream SETTINGS_INITIAL_WINDOW_SIZE reduction from the peer. RFC 9113 6.9.2 requires the sender to shift every stream's flow-control window by (new - old), which can drive the window negative; a compliant sender must then stop sending DATA until WINDOW_UPDATEs bring it back above zero. Bun dropped the decrease entirely and treated the following WINDOW_UPDATEs as fresh credit, so it kept uploading into a window the peer had just made deeply negative. A compliant peer is entitled to answer with FLOW_CONTROL_ERROR, killing the stream or the whole session.

Repro

Raw net server lets the client fill the default 65535-byte stream window, then sends SETTINGS{INITIAL_WINDOW_SIZE:10} followed by 40 WINDOW_UPDATE(stream,1000). The stream window is now 10 + 40000 - 65535 = -25525. Node sends 0 DATA bytes; Bun sent every remaining queued byte.

DATA bytes sent while the window was negative: 34465  (before)
DATA bytes sent while the window was negative: 0      (after / node)

Cause

The inbound engine already tracks a signed send window and applies the 6.9.2 delta correctly. Outbound DATA, however, is still gated by the legacy per-stream remote_window_size / remote_used_window_size u64 pair. The engine-to-legacy bridge in on_remote_settings only touched remote_window_size when new_iws >= stream.remote_window_size and then overwrote it with new_iws instead of adding the delta, so a decrease was a no-op and an increase lost any prior WINDOW_UPDATE credit.

Fix

Capture the previous remote initial_window_size, compute delta = new - old, and apply it to every stream's remote_window_size unconditionally. Because available = remote_window_size.saturating_sub(remote_used_window_size), a negative effective window already reads as 0 with no type change needed. Applied at all three remote-SETTINGS sites for consistency (the on_remote_settings Sink impl is the live path; the two in handle_settings_frame are in the retired legacy inbound half).

Test

test/js/node/http2/node-http2.test.js gains a raw-server conformance case that drives the client through the sequence above, uses a PING round-trip to deterministically fence "after the client processed the SETTINGS+WUs", and asserts { dataAtShrink: 65535, bytesSentWhileNegative: 0 }. Fails with 34465 on main, passes with the fix, then reopens the window and asserts the full body drains.

Fixes #30342

…d flow control

RFC 9113 6.9.2 says a change to SETTINGS_INITIAL_WINDOW_SIZE shifts every
stream's send window by (new - old), and the window can go negative. The
legacy outbound bridge only applied the change when the new value was at
least the current window and overwrote rather than adding the delta, so a
mid-stream reduction was dropped and subsequent WINDOW_UPDATEs were spent
as fresh credit instead of paying down the deficit. Bun kept uploading
into a window the peer had just driven negative, inviting a
FLOW_CONTROL_ERROR from any compliant peer.

Apply the signed delta to every stream's remote_window_size on each remote
SETTINGS. The cumulative (granted, used) u64 pair with saturating_sub
already yields 0 when the effective window is negative, so no type change
is needed.
@coderabbitai

coderabbitai Bot commented Jul 7, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 14 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 92e317bc-9a59-4020-81f6-29b285b223ec

📥 Commits

Reviewing files that changed from the base of the PR and between 3f67971 and c952ad4.

📒 Files selected for processing (2)
  • src/runtime/api/bun/h2_frame_parser.rs
  • test/js/node/http2/node-http2-flow-control.test.ts

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the claude label Jul 7, 2026
@robobun

robobun commented Jul 7, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 4:17 AM PT - Jul 7th, 2026

❌ @robobun, your commit c952ad4 has some failures in Build #69685 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 33602

That installs a local version of the PR into your bun-33602 executable, so you can run:

bun-33602 --bun

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. HTTP/2 Flow Control Bug in node:http2 - Requests Hang Indefinitely #30342 - Directly describes the same root cause: handleSettingsFrame() not applying the INITIAL_WINDOW_SIZE delta to existing streams, causing requests with bodies >65535 bytes to hang indefinitely

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #30342

🤖 Generated with Claude Code

An increase must be added on top of prior WINDOW_UPDATE credit, not
overwritten with the new initial value. Fails on main with 200000 sent
out of 210000 granted; the fix drains the full body.
Comment thread test/js/node/http2/node-http2.test.js Outdated
Comment thread src/runtime/api/bun/h2_frame_parser.rs Outdated
Comment thread src/runtime/api/bun/h2_frame_parser.rs Outdated
robobun added 2 commits July 7, 2026 04:05
… wire reject paths

Moved the two client flow-control tests to node-http2-flow-control.test.ts
so the gate does not run into the pre-existing 10k-request maxSessionMemory
stress test that sits at its 150s debug timeout in node-http2.test.js.
Shared the raw-server frame parser between the two tests and gave every
awaited promise a reject path wired to the session error event.
Comment thread test/js/node/http2/node-http2-flow-control.test.ts Outdated
@robobun

robobun commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

CI status: the only hard failure across builds #69643 and #69685 is buildkite-agent artifact download timed out after 120s on the darwin-aarch64-26 test lane, which never reaches the test step. The remaining annotations are pre-existing Windows flakes in bun-install.test.ts, update_interactive_install.test.ts, and spawn.test.ts, none touching http2.

The two new test/js/node/http2/node-http2-flow-control.test.ts cases fail on main (bytesSentWhileNegative: 34465 vs expected 0; 200000 vs expected 210000) and pass with this change, verified locally under both debug+ASAN and release. Ready for a maintainer to merge or re-kick the darwin lane.

@robobun

robobun commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #31584. #30342 is now closed as fixed on main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HTTP/2 Flow Control Bug in node:http2 - Requests Hang Indefinitely

1 participant