Skip to content

Bugfix HTTP/2 keep_alive_max_requests dropping the requests in flight - #377

Open
harryherbig wants to merge 1 commit into
pgjones:mainfrom
harryherbig:h2-keep-alive-max-requests
Open

harryherbig wants to merge 1 commit into
pgjones:mainfrom
harryherbig:h2-keep-alive-max-requests

Conversation

@harryherbig

@harryherbig harryherbig commented Sep 22, 2026 •

Copy link
Copy Markdown

On HTTP/2, reaching keep_alive_max_requests called close_connection() right away, on the request that exceeded the limit. h2 moves to the CLOSED state on GOAWAY, where it refuses to send HEADERS or DATA, so every stream still in flight on that connection lost its response (stream_send swallows the ProtocolError). The GOAWAY's last_stream_id covers those streams, so the client cannot safely retry them either.

With a multiplexing client that keeps one long-lived connection (grpc, Envoy, browsers) this fails the 1001st request plus everything in flight next to it, every 1000 requests. Measured with a grpcio client sending 3000 unary calls at concurrency 50 over one connection:

keep_alive_max_requests before after
1000 (default) 99 failed with UNAVAILABLE 0
100 988 failed 0

The fix mirrors the shutdown path that already exists in H2Protocol: when the limit is reached, announce SETTINGS_MAX_CONCURRENT_STREAMS = 0, keep serving the streams already in flight (including ones the client sent before it saw the setting), and send the GOAWAY once the connection is idle. The connection is then closed by the server, as the HTTP/1.1 path does after connection: close, instead of lingering until the keep-alive timeout. This is the HTTP/2 counterpart of #227.

test_protocol_keep_alive_max_requests now drives three streams into the protocol with a limit of two and asserts the sequence: settings go to zero, nothing is reset or terminated while streams are open, GOAWAY and Closed follow the last StreamClosed.

No httpx end-to-end test: httpcore ignores a max_concurrent_streams of zero (if max_concurrent_streams: in _receive_remote_settings_change) and h2 then raises LocalProtocolError on the client side for any further request in the drain window. grpc-core and Envoy handle the setting as intended.

This branch has not been deployed

No deployments
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