Repository navigation
fix(http2): reset only the stream whose request is over the body limit - #6389
Open
binaryfire wants to merge 3 commits into
Open
binaryfire wants to merge 3 commits into
binaryfire wants to merge 3 commits into
Conversation
When a request body went over package_max_length, the server sent a malformed RST_STREAM: stream id 0, the ACK flag and a zero length, followed by the 4-byte error code. Peers read that as a connection error and parsed the error code as the next frame header. DATA the client had already sent for the stream then failed with "stream not found" and the server closed the connection. A content-length over the limit sent a 413 and GOAWAY and closed the connection, and DATA for a stream refused by max_concurrent_streams also closed it. Every other request on the connection was lost. Now the stream gets a complete 413 response and RST_STREAM(NO_ERROR) (RFC 9113 section 8.1), and the connection stays open: - DATA for a closed stream is discarded, but still counts against the connection flow-control window (RFC 9113 sections 5.1 and 6.9). - A header block on a closed stream, such as trailers sent before the client got the reset, is decoded to keep the HPACK state and then discarded. A block with pseudo-headers is a new request on a closed stream and is still a connection error. - A content-length over the limit sends the 413, decodes the rest of the block and resets the stream once the block ends.
The async server parses requests outside a coroutine, so it can't wait for output-buffer space. A 413 sent while the buffer is full is lost after its header block has gone through the connection's HPACK encoder, and the client can no longer decode later headers. A failed 413 now closes the connection, like the other sends made while parsing. Checking only the RST_STREAM that follows isn't enough. In PROCESS mode the reactor can drain the buffer between the two sends, and when the header block isn't finished the reset waits for a later CONTINUATION.
A large response fills the server's output buffer, then the client sends a content-length over the limit, the same in an unfinished header block, or DATA over the limit. Each must close the connection without restarting the worker, in BASE and PROCESS modes.
binaryfire
force-pushed
the
fix/http2-oversized-body-stream-reset
branch
from
November 12, 2026 04:48
e8ce96d to
d25f196
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #6389 +/- ##
==========================================
- Coverage 84.08% 84.07% -0.01%
==========================================
Files 115 115
Lines 20237 20237
Branches 3637 3638 +1
==========================================
- Hits 17016 17015 -1
- Misses 3221 3222 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.
When one HTTP/2 request goes over
package_max_length, the server closes the whole connection, and every other request on it fails:content-lengthover the limit. The server sends a 413, then GOAWAY, and closes the connection.http2_max_concurrent_streamsis reset with REFUSED_STREAM, then its DATA hits the same "stream not found" error.Fix
An oversized request gets a complete 413 response, then RST_STREAM(NO_ERROR) so the client stops the upload (RFC 9113 §8.1). A refused stream still gets RST_STREAM(REFUSED_STREAM). Either way, the connection stays open:
content-lengthover the limit sends the 413, decodes the rest of the block, and resets the stream once the block ends.http2_server_send_rst_stream()is removed;Http2Stream::reset()already builds a correct frame.nginx does the same: it counts DATA for closed streams against the connection window before ignoring it, and sends RST_STREAM(NO_ERROR) after the response when the upload is still open.
Hypervel impact
Hypervel enables HTTP/2 by default. A gRPC message over
package_max_lengthdrops every other call on the same connection. Hypervel's regression test sends an oversized POST and a GET on one connection, and checks that the GET is answered.Tests
request_body_limit.phptcovers all three cases in BASE, PROCESS and coroutine server modes. It checks that an oversized request gets a 413 and RST_STREAM(NO_ERROR), that a refused stream gets only RST_STREAM(REFUSED_STREAM), that another request on the connection is answered, that trailers and CONTINUATION sent after the reset still update the HPACK table, and that discarded DATA returns connection credit. It fails on master.request_body_limit_output_buffer_full.phptfills the output buffer with a large response, then checks that each oversized case closes the connection without restarting the worker, in BASE and PROCESS modes.Also run: the HTTP/2 server, coroutine HTTP server and HTTP/2 client suites, both new tests under Valgrind, and both new tests plus
request_trailer*.phpton a ZTS build with thread support.