Skip to content

h3: replenish bidi stream credit on stream close#79

Merged
EdmondDantes merged 1 commit into
mainfrom
h3-replenish-bidi-stream-credit
Jun 1, 2026
Merged

h3: replenish bidi stream credit on stream close#79
EdmondDantes merged 1 commit into
mainfrom
h3-replenish-bidi-stream-credit

Conversation

@EdmondDantes
Copy link
Copy Markdown
Contributor

Problem

HttpArena baseline-h3/static-h3 at c=64 collapsed to ~1277 req/s (≈20 req/s per connection), server idle (CPU 12.7%), 0 packet loss, TTFB ~4ms. Same h2load client drove frankenphp-trueasync to 201k → the bottleneck is our H3 path.

Root cause

stream_close_cb closed the nghttp3 stream but never called ngtcp2_conn_extend_max_streams_bidi(). ngtcp2 does not auto-send MAX_STREAMS on close — the application must. So each QUIC connection was permanently capped at initial_max_streams_bidi (default 100) bidi request streams; after 100 the client could not open another stream and the connection stalled.

Bench arithmetic confirms it: 10496 started = 64 conn × (100 done + 64 in-flight).

Fix

Call ngtcp2_conn_extend_max_streams_bidi(conn, 1) for each client-initiated bidi stream (id & 3 == 0) in stream_close_cb.

Verification

  • Single connection, H3CLIENT_REQUEST_COUNT=150: pre-fix response_submitted stalls at 100; post-fix all 150 complete.
  • A/B c=64 (-m 32, n=64000): 6400 done/30s → 60000 done/10s.
  • New test 036-h3-stream-credit-replenish; full H3 suite 36/36 green.

ngtcp2 does not auto-extend MAX_STREAMS when a stream closes, so the
server must call ngtcp2_conn_extend_max_streams_bidi() itself. Without
it every QUIC connection was permanently capped at
initial_max_streams_bidi (default 100) request streams: each served
fast (TTFB ~4ms), then the connection stalled with the server idle.
HttpArena baseline-h3/static-h3 collapsed to ~20 req/s per connection
(1277 total at c=64) while frankenphp-trueasync hit 201k on the same
client.

Call ngtcp2_conn_extend_max_streams_bidi(conn, 1) for each
client-initiated bidi stream (id & 3 == 0) in stream_close_cb.

A/B at c=64 (-m 32, n=64000): 6400 done in 30s -> 60000 done in 10s.
Add test 036 driving 150 sequential streams over one reused QUIC
connection; pre-fix it stalls at 100.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

Coverage

Total lines: 81.21% → 81.41% (+0.19 pp)

File Baseline Current Δ Touched
src/core/http_connection.c 68.03% 70.02% +1.99 pp
src/http3/http3_callbacks.c 79.76% 81.41% +1.64 pp

@EdmondDantes EdmondDantes merged commit 0f0b8fa into main Jun 1, 2026
8 checks passed
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