Repository navigation
fix(http2): never write RST_STREAM ahead of the stream's still-queued HEADERS - #2008
Conversation
079b3e0 to
c720a11
Compare
c720a11 to
34dfcb6
Compare
34dfcb6 to
81d7268
Compare
81d7268 to
262a8a3
Compare
PR HealthLicense Headers ✔️
All source files should start with a license header. Unrelated files missing license headers
This check can be disabled by tagging the PR with Unused Dependencies ✔️
For details on how to fix these, see dependency_validator. This check can be disabled by tagging the PR with Breaking changes ✔️
This check can be disabled by tagging the PR with API leaks ✔️The following packages contain symbols visible in the public API, but not exported by the library. Export these symbols or remove them from your publicly visible API.
This check can be disabled by tagging the PR with Coverage ✔️
This check for test coverage is informational (issues shown here will not fail the PR). This check can be disabled by tagging the PR with Changelog Entry ✔️
Changes to files need to be accounted for in their respective changelogs. This check can be disabled by tagging the PR with |
… HEADERS > [!NOTE] > This PR was generated by an AI coding agent (Jetski) on behalf of @mosuem. ### Summary `StreamHandler._terminateStream` (what `TransportStream.terminate()` ends up in) wrote the `RST_STREAM(CANCEL)` straight to the `FrameWriter`, bypassing `ConnectionMessageQueueOut`. If the stream's opening `HEADERS` were still sitting in that queue — because the socket was applying backpressure, or because the queue was head-of-line blocked behind flow-controlled `DATA` of another stream — the peer received `RST_STREAM` for a stream it had never seen, which is a connection error (RFC 9113 Section 6.4: *"RST_STREAM frames MUST NOT be sent for a stream in the "idle" state. If a RST_STREAM frame identifying an idle stream is received, the recipient MUST treat this as a connection error of type PROTOCOL_ERROR"*). A client cancelling a request shortly after starting it could thus take down the whole connection. ### Changes - **`lib/src/flowcontrol/connection_queues.dart`**: Add `ConnectionMessageQueueOut.cancelStreamMessages(streamId)`, which drops the stream's queued `DataMessage`s (they would be discarded by the peer anyway) and reports whether a `HeadersMessage` for the stream is still queued. - **`lib/src/streams/stream_handler.dart`**: In `_terminateStream`, enqueue a `ResetStreamMessage` behind the queued `HEADERS` when there are any; otherwise write the `RST_STREAM` directly as before. ### Test Verification (Fails Before $\rightarrow$ Passes After) Added `rst-stream-is-sent-after-queued-headers-of-the-stream` in `test/client_test.dart`. The test keeps the client's `FrameWriter` in its "would buffer" state (the server's `StreamIterator` pauses the frame stream after each frame, and that pause propagates synchronously into the client's `BufferedSink`), opens a stream and cancels it immediately, then checks the wire order. **Before fix:** ```text 00:00 +0 -1: client-tests client-errors rst-stream-is-sent-after-queued-headers-of-the-stream [E] Expected: <Instance of 'HeadersFrame'> with `streamId`: <3> Actual: <Instance of 'RstStreamFrame'> Which: is not an instance of 'HeadersFrame' ``` **After fix:** ```text 00:00 +1: All tests passed! ```
262a8a3 to
c3e3058
Compare
…mosum/http2-4-control-frame-queue-and-rst-order # Conflicts: # pkgs/http2/CHANGELOG.md
Revisions updated by 19 package dependencies found. Moving forward revisions for: http. Commit message: [deps] rev http Revisions updated by `dart tools/rev_sdk_deps.dart http`. http (https://github.com/dart-lang/http/compare/c76dfb3..c76dfb3): c76dfb3 Wed Oct 28 10:21:55 2026 -0700 Brian Quinlan fix(http2): accept larger headers (dart-lang/http#2041) 23495b0 Mon Oct 26 15:17:28 2026 -0700 Brian Quinlan chore(cronet_http): ready 1.10.1 (dart-lang/http#2039) 27591d4 Mon Oct 26 15:08:10 2026 -0700 Brian Quinlan fix(cronet): avoid cronet namespace duplication (dart-lang/http#2034) 9cdb8da Fri Oct 23 11:14:21 2026 -0700 Kevin Moore Tighten pull_request_label workflow permissions (dart-lang/http#2035) 3998b87 Fri Oct 23 07:31:29 2026 +0200 Moritz fix(http2): do not head-of-line block HEADERS, RST_STREAM and GOAWAY behind window-exhausted DATA (dart-lang/http#2013) be2b51f Fri Oct 23 07:31:28 2026 +0200 Moritz fix(http2): Http2Client applies response-body backpressure to the HTTP/2 stream and uses 4 MiB / 16 MiB receive windows (dart-lang/http#2029) 3b504cf Thu Oct 22 06:28:12 2026 +0200 Moritz fix(http2): never write RST_STREAM ahead of the stream's still-queued HEADERS (dart-lang/http#2008) a641fac Thu Oct 22 06:28:12 2026 +0200 Moritz fix(http2): preserve the complete response on RST_STREAM(NO_ERROR) (dart-lang/http#2012) 5d896c7 Thu Oct 22 06:28:11 2026 +0200 Moritz fix(http2): keep flow-controlled end-stream DATA alive until it is flushed (`#1998`) (dart-lang/http#2007) c834aa0 Thu Oct 22 06:28:11 2026 +0200 Moritz fix(http2): ignore PRIORITY and unknown frames on streams and reject non-zero stream control frames (dart-lang/http#2006) 2d35515 Thu Oct 22 06:15:59 2026 +0200 Moritz fix(http2): replenish connection receive window when stream closes with unconsumed data (dart-lang/http#2005) 1217181 Tue Oct 20 19:17:59 2026 -0400 Mouad Debbar [http_multi_server] Prepare to release 3.2.3 (dart-lang/http#2028). http (https://github.com/dart-lang/http/compare/a8a24a0..c76dfb3): c76dfb3 Wed Oct 28 10:21:55 2026 -0700 Brian Quinlan fix(http2): accept larger headers (dart-lang/http#2041) 23495b0 Mon Oct 26 15:17:28 2026 -0700 Brian Quinlan chore(cronet_http): ready 1.10.1 (dart-lang/http#2039) 27591d4 Mon Oct 26 15:08:10 2026 -0700 Brian Quinlan fix(cronet): avoid cronet namespace duplication (dart-lang/http#2034) 9cdb8da Fri Oct 23 11:14:21 2026 -0700 Kevin Moore Tighten pull_request_label workflow permissions (dart-lang/http#2035) 3998b87 Fri Oct 23 07:31:29 2026 +0200 Moritz fix(http2): do not head-of-line block HEADERS, RST_STREAM and GOAWAY behind window-exhausted DATA (dart-lang/http#2013) be2b51f Fri Oct 23 07:31:28 2026 +0200 Moritz fix(http2): Http2Client applies response-body backpressure to the HTTP/2 stream and uses 4 MiB / 16 MiB receive windows (dart-lang/http#2029) 3b504cf Thu Oct 22 06:28:12 2026 +0200 Moritz fix(http2): never write RST_STREAM ahead of the stream's still-queued HEADERS (dart-lang/http#2008) a641fac Thu Oct 22 06:28:12 2026 +0200 Moritz fix(http2): preserve the complete response on RST_STREAM(NO_ERROR) (dart-lang/http#2012) 5d896c7 Thu Oct 22 06:28:11 2026 +0200 Moritz fix(http2): keep flow-controlled end-stream DATA alive until it is flushed () (dart-lang/http#2007) c834aa0 Thu Oct 22 06:28:11 2026 +0200 Moritz fix(http2): ignore PRIORITY and unknown frames on streams and reject non-zero stream control frames (dart-lang/http#2006) 2d35515 Thu Oct 22 06:15:59 2026 +0200 Moritz fix(http2): replenish connection receive window when stream closes with unconsumed data (dart-lang/http#2005) 1217181 Tue Oct 20 19:17:59 2026 -0400 Mouad Debbar [http_multi_server] Prepare to release 3.2.3 (dart-lang/http#2028) Change-Id: I259130f06771f4b7ba61c7610847e6677b5c8ed5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/570641 Reviewed-by: Nate Bosch <[email protected]> Commit-Queue: Brian Quinlan <[email protected]>
Note
This PR was generated by an AI coding agent (Jetski) on behalf of @mosuem.
Summary
StreamHandler._terminateStream(whatTransportStream.terminate()ends up in) wrote theRST_STREAM(CANCEL)straight to theFrameWriter, bypassingConnectionMessageQueueOut. If the stream's openingHEADERSwere still sitting in that queue — because the socket was applying backpressure, or because the queue was head-of-line blocked behind flow-controlledDATAof another stream — the peer receivedRST_STREAMfor a stream it had never seen, which is a connection error (RFC 9113 Section 6.4: "RST_STREAM frames MUST NOT be sent for a stream in the "idle" state. If a RST_STREAM frame identifying an idle stream is received, the recipient MUST treat this as a connection error of type PROTOCOL_ERROR"). A client cancelling a request shortly after starting it could thus take down the whole connection.Changes
lib/src/flowcontrol/connection_queues.dart: AddConnectionMessageQueueOut.cancelStreamMessages(streamId), which drops the stream's queuedDataMessages (they would be discarded by the peer anyway) and reports whether aHeadersMessagefor the stream is still queued.lib/src/streams/stream_handler.dart: In_terminateStream, enqueue aResetStreamMessagebehind the queuedHEADERSwhen there are any; otherwise write theRST_STREAMdirectly as before.Test Verification (Fails Before$\rightarrow$ Passes After)
Added
rst-stream-is-sent-after-queued-headers-of-the-streamintest/client_test.dart. The test keeps the client'sFrameWriterin its "would buffer" state (the server'sStreamIteratorpauses the frame stream after each frame, and that pause propagates synchronously into the client'sBufferedSink), opens a stream and cancels it immediately, then checks the wire order.Before fix:
After fix: