http2: stream the request body to the application - #3717
Merged
Conversation
benoitc
force-pushed
the
fix/h2-stream-body
branch
8 times, most recently
from
August 30, 2026 07:55
32e1b07 to
9e9de1a
Compare
The whole body was buffered before dispatch and window credit was returned as frames arrived, so a client could grow a worker without the application being called. Dispatch on headers, let the application pull the body off the stream as it reads, and return window credit only for what it consumed.
benoitc
force-pushed
the
fix/h2-stream-body
branch
from
August 30, 2026 09:35
9e9de1a to
da7d2e3
Compare
A shared H2Connection subclass keeps the state open on GOAWAY(NO_ERROR) so established streams finish and later ones are refused.
Wait at most cfg.timeout, handle frames read meanwhile in order, reset the stream with CANCEL on expiry and make the WSGI response raise.
Refuse it at the connection level and reset the stream with INTERNAL_ERROR instead, so the HPACK table stays intact.
Read with cfg.keepalive between requests, closing with GOAWAY, and cfg.timeout while a body is pulled, cancelling the stream on expiry.
Remove them on RST_STREAM and never hand out a request whose stream is closed, so HEADERS+RST_STREAM floods cannot grow the worker.
Queue an empty DATA frame carrying END_STREAM in both connection classes.
receive() waits for the peer to go away instead of returning again at once, and the receive loop resumes a reader paused under backpressure.
benoitc
force-pushed
the
fix/h2-stream-body
branch
from
August 30, 2026 14:03
d653546 to
4d2f53c
Compare
…settings doc The tests took the write lock before the app was dispatched, which deadlocked on Python 3.10 and 3.11 scheduling; the app now signals before the lock is taken and is released with a second event.
A docker suite runs the official h2spec image against gthread, gevent and asgi services, failing on any case outside a per-worker list of known gaps, with a CI job on HTTP/2 changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The whole HTTP/2 body was buffered before dispatch and window credit was returned as frames arrived, so a client could grow a worker without the application being called.
The request is now dispatched on its headers, the application pulls the body off the stream as it reads (
wsgi.inputor ASGIreceive()), and window credit is returned only for what it consumed. A body left unread is cut off withRST_STREAM(NO_ERROR)after the response. Streams on an ASGI connection are served concurrently. Replaces #3716.Follow-up commits fix the blockers a review of the HTTP/2 support found:
GOAWAYis honoured wherever it lands in a read, through a sharedH2Connectionsubclass instead of forcing h2's statetimeout; a peer that stops reading getsRST_STREAM(CANCEL)and the application is stopped, not told the response succeededHEADERSblock that corrupted the HPACK tablekeepalive, stalled bodies are cancelled aftertimeout, and streams reset before they are served are droppedreceive()after the body blocks until the peer goes instead of spinning the loop, a paused reader is resumed, and in-flight streams get the disconnect grace periodVerified with the unit suite, the docker
http2andasgi_compliancesuites, h2spec (asgi 146/146; gthread 142, gevent 143 with pre-existing gaps), and live checks per worker.