Discard late HEADERS for locally reset streams - #33
Merged
ioquatix merged 2 commits intoSep 27, 2026
Merged
Conversation
When a stream is reset locally, e.g. by cancelling a request, the remote peer may already have response headers in flight. These were treated as a new incoming stream and failed the connection with "Invalid stream id". They are now decoded, to keep the HPACK decoder state synchronized (RFC 9113 §5.1), and discarded. Also set the stream ID of generated `CONTINUATION` frames, which previously defaulted to 0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Contributor
Author
|
@ioquatix Would you mind taking a look at this? Feel free to fix in your own way if you prefer, I just thought I'd put up a proposal! |
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Member
|
Thanks, this looks reasonable to me. |
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.
Summary
HEADERSreceived for a locally-initiated stream that has already been closed, instead of treating them as a new incoming stream.CONTINUATIONframes generated byContinued#pack, which previously defaulted to0.Rationale
When a client resets a stream, e.g. with
RST_STREAM(CANCEL)after a request is interrupted, the server may already have the response headers in flight.Connection#receive_headersdid not find the stream in@streams, fell through toaccept_stream, and failed the whole connection withProtocolError: Invalid stream id, taking every other in-flight request on the connection down with it. This surfaced with the cancellation cleanup in socketry/async-http#251.RFC 9113 §5.1 requires an endpoint to process the connection-level effects of frames that arrive after it sent
RST_STREAM. ForHEADERS, that means the header block must still be HPACK-decoded, otherwise the decoder's dynamic table diverges from the peer's encoder and later responses are decoded incorrectly. The decoded headers are then discarded, and the stream is not recreated. This follows the existingclosed_stream_id?pattern inreceive_data.The following are unchanged and still raise:
HEADERSon stream 0.HEADERSfor idle or invalid stream IDs.COMPRESSION_ERROR).CONTINUATIONsequences.While adding coverage for split header blocks, I found that
Continued#packcreatedCONTINUATIONframes with stream ID0, so a peer would reject any header block larger than the maximum frame size.🤖 Generated with Claude Code