Visitar URL original
Ignore trailers on a stream we have reset by kazu-yamamoto · Pull Request #224 · kazu-yamamoto/http2 · GitHub
Skip to content

Ignore trailers on a stream we have reset - #224

Merged
kazu-yamamoto merged 1 commit into
mainfrom
ignore-headers-on-reset-stream
Oct 17, 2026
Merged

kazu-yamamoto merged 1 commit into
mainfrom
ignore-headers-on-reset-stream

Conversation

@kazu-yamamoto

Copy link
Copy Markdown
Owner

Problem

RFC 9113 §5.1: "An endpoint MUST ignore frames that it receives on closed streams after it has sent a RST_STREAM frame."

A peer that sends a body followed by trailers (gRPC, for example) may already have sent the trailers when our RST_STREAM reaches it. The server, however, removes a stream from the table when it resets it. HEADERS arriving on that stream ID afterwards were treated as reuse of a stream identifier, which is a connection error, so every other stream on the connection was lost too.

Fix

  • closed records the IDs of peer-initiated streams that the server resets (ResetByMe). It keeps the latest 1000 IDs and forgets the oldest first. Server only.
  • In getOddStream, HEADERS and CONTINUATION on a remembered stream are ignored. Their field block still goes through hpackDiscardHeader, so the HPACK dynamic table stays in sync.
  • HEADERS on any other closed stream is still a connection error.

Tests

New tests in test/HTTP2/ServerSpec.hs:

  • Ignored trailers. The server resets a request whose body outgrows its content-length (Check the body against its content-length before delivering it #223). The client's trailers then arrive, carrying a field with incremental indexing, and the next request refers to that dynamic table entry. Expected: RST_STREAM 1 ProtocolError, then HEADERS 3, with no GOAWAY. This covers both a single HEADERS frame and HEADERS split with a CONTINUATION.
  • Regression guard. After a response has completed normally, HEADERS on the same stream still gets GOAWAY ProtocolError.

Checks:

  • Without the fix, the first test fails with a GOAWAY.
  • Without CONTINUATION handling, the split case fails.
  • Ignoring every HEADERS on closed streams makes the guard fail.
  • Both tests passed 20 out of 20 runs under CPU load.
  • cabal test all passes, and I ran it three times. There are no compiler warnings.

🤖 Generated with Claude Code

RFC 9113, section 5.1: "An endpoint MUST ignore frames that it receives
on closed streams after it has sent a RST_STREAM frame."  A peer sending
a body and trailers -- gRPC, say -- may have sent the trailers before
our reset reached it.  But once the stream is gone from the table,
HEADERS on it were taken for the reuse of a stream identifier, a
connection error, and every other stream on the connection went with it.

The server now remembers the latest 1000 streams of the peer's it has
reset, and ignores HEADERS and CONTINUATION on them, decoding the field
block for the dynamic table.  HEADERS on any other closed stream is
still a connection error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kazu-yamamoto
kazu-yamamoto merged commit 6b9e0d1 into main Oct 17, 2026
6 of 8 checks passed
@kazu-yamamoto
kazu-yamamoto deleted the ignore-headers-on-reset-stream branch October 17, 2026 08:02
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