HTTP/2 body pipe sends 1-byte DATA frames when a stream holds a sliver of capacity
Maintainers usually reply within 2 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 74/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- rust
- Domain
- networking, performance
Research direction
Start in src/proto/h2/mod.rs at PipeToSendStream, then run the regression test h2_chunk_waits_for_useful_capacity_instead_of_sliver_frames from the linked PR. Trace how capacity is claimed before send_data; done means Stream B no longer emits a 1-byte initial DATA frame when only a sliver of connection capacity is available.
Written by the indexing model from the issue text.
Description
Version
hyper 1.9.0 through master (c954d80), with h2 0.4.19.
Platform
Any. Seen on Linux and macOS.
Description
When a stream holds only a tiny amount of send capacity, the HTTP/2 client and server body pipe (PipeToSendStream in src/proto/h2/mod.rs) hands a body chunk to h2 anyway, and h2 splits it into DATA frames sized to that capacity. The body then goes out as a 1-byte DATA frame, followed by more small frames as capacity trickles in (silly-window syndrome).
On master, the pipe reserves a 1-byte claim once a chunk is in hand, then calls send_data as soon as capacity() > 0. hyper 1.9.0 reserved the byte before polling the body; the result is the same. On a connection whose window is nearly used up, that one byte is often all the stream has been assigned, so the first frame carries a single byte of a 10 KB chunk.
This used to cost only framing overhead. Since h2 0.4.16, it breaks connections. h2 servers (tonic, axum, hyper) now charge every non-final DATA frame under 256 bytes against a per-connection budget (DEFAULT_DATA_FRAME_OVERHEAD_THRESHOLD, a minimum of 25600). When that budget runs out they send GOAWAY(ENHANCE_YOUR_CALM, "too_many_data_frames"), and every in-flight stream on the connection fails.
We hit this in a reverse proxy that keeps pooled HTTP/2 connections to a hyper backend with adaptive_window(true). Adaptive windows start at 65535, so a busy pooled connection often uses up its connection window. A frame trace showed about 2,900 frames of sz=10240, available=1 in 4 seconds, and the backend GOAWAY'd the pooled connection every few seconds.
Reproduction
The regression test in the linked PR (h2_chunk_waits_for_useful_capacity_instead_of_sliver_frames) reproduces it deterministically:
- Stream A sends 65534 bytes, which the server does not release, leaving one byte of connection window.
- Stream B then sends a 10 KB body.
- On
master, stream B's first DATA frame is 1 byte.
Proposed fix
Keep the small claim that #4003 depends on, but make it big enough to cut a useful frame. Reserve min(len, 1024) and wait until that much is assigned before calling send_data. 1 KiB is well below any stream window a real peer advertises, so this cannot hold a chunk back indefinitely, and it rules out sub-256-byte frames.
- Dominant language
- Rust
- Stars
- 16.3k
- Forks
- 1.8k
- Avg merge
- 6d 7h
- Merged PRs (30d)
- 18
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from hyperium/hyper
-
A-docs C-bug E-easy E-pr-welcome K-hyper-util
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
hyperium/hyper#4221 · 1 comment ·
Maintainers usually reply within 2 days
-
C-feature
Difficulty 1/5 Under an hour Newbie friendliness 65/100
hyperium/hyper#2652 · 4 reactions ·
Maintainers usually reply within 2 days
-
Upgraded HTTP/2 CONNECT streams cannot be reset, so a failed tunnel looks like a clean closePossibly taken @jeremyjpj0916 claimed this 8 days ago. Open
Difficulty 4/5 3-5 days Newbie friendliness 55/100
Maintainers usually reply within 2 days
-
HTTP/1 client: `SendRequest::is_ready()` can stay true while a request is in flightPossibly taken @shodoco claimed this 8 days ago. Open
Difficulty 3/5 1-2 days Newbie friendliness 35/100
hyperium/hyper#4207 · 1 comment ·
Maintainers usually reply within 2 days
-
hyper-util legacy client: an HTTP/1 request can hang forever when the connection closes while the request is being queuedPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 4/5 3-5 days Newbie friendliness 62/100
hyperium/hyper#4202 · 1 comment ·
Maintainers usually reply within 2 days
Similar issues
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
zcashlabs/thus-spoke-zakura#153 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 79/100
topgrade-rs/topgrade#2395 ·
Maintainers usually reply within 1 day
-
app bug windows-os
Difficulty 2/5 1-3 hours Newbie friendliness 67/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
matrix-org/matrix-rust-sdk#7217 ·
Maintainers usually reply within 1 day
-
editor good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
funnyboy-roks/inq#54 ·