Skip to content

fix(http2): avoid buffering Upgraded writes without send capacity#4102

Open
seanmonstar wants to merge 3 commits into
masterfrom
sean/lsnkptrokswk
Open

fix(http2): avoid buffering Upgraded writes without send capacity#4102
seanmonstar wants to merge 3 commits into
masterfrom
sean/lsnkptrokswk

Conversation

@seanmonstar

Copy link
Copy Markdown
Member

This continues #4050, with changes based on the feedback I left on the PR.

sunhuanran and others added 3 commits June 15, 2026 14:52
…adedSendStreamTask

Fix a backpressure bypass bug in UpgradedSendStreamTask::tick() where
poll_capacity() returning Poll::Pending caused a 'break 'capacity' that
fell through to rx.poll_next() -> send_data(), pushing data into the h2
send buffer without available capacity. This broke the HTTP/2 flow
control chain, causing unbounded memory growth (OOM) when downstream
consumers were slower than upstream producers.

The fix changes 'break 'capacity' to 'return Poll::Pending', which
correctly suspends the task until a WINDOW_UPDATE frame restores send
capacity. The now-unused 'capacity label is also removed.

This bug was introduced in hyper v1.8.0 (PR #3967) and affects
v1.8.0, v1.8.1, and v1.9.0. A single HTTP/2 CONNECT tunnel with
asymmetric upstream/downstream speeds could trigger OOM within seconds.

Add four integration tests covering H2 CONNECT backpressure scenarios:
- h2_connect_backpressure_respected: small window + large data transfer
- h2_connect_zero_window_then_release: normal path regression guard
- h2_connect_reset_during_backpressure: RST_STREAM error propagation
- h2_connect_backpressure_bidirectional: bidirectional data + backpressure
When poll_capacity returns Pending, defer the return and check
poll_reset first. This ensures the reset waker is registered via
poll_reset (which shares the same send_task slot in h2 internally),
enabling earlier RST_STREAM detection without an extra poll round-trip.

While h2 internally calls notify_send() on RST_STREAM/EOF/error
(so poll_capacity's waker alone would eventually be woken), polling
poll_reset here provides immediate detection if RST_STREAM has
already arrived, avoiding one unnecessary suspend/wake cycle.

The h2_has_capacity flag cleanly separates the capacity check from
the control flow, making the manual select-over-3-futures pattern
more readable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant