Skip to content

h2: Guard against KeyError for data received on a closed stream#365

Closed
gonzageraci wants to merge 1 commit into
pgjones:mainfrom
gonzageraci:fix-h2-data-after-close
Closed

h2: Guard against KeyError for data received on a closed stream#365
gonzageraci wants to merge 1 commit into
pgjones:mainfrom
gonzageraci:fix-h2-data-after-close

Conversation

@gonzageraci

Copy link
Copy Markdown

What

The HTTP/2 protocol crashes the connection with a KeyError when a DataReceived event arrives for a stream that is no longer tracked in self.streams.

Why

When an application sends its complete response and the stream is closed (removed from self.streams via _close_stream) before the client has finished sending the request body, the h2 state machine can still emit DataReceived events for that stream. The DataReceived branch in _handle_events indexes self.streams[event.stream_id] without guarding for a missing key, so the lookup raises KeyError and the connection task dies with Unhandled exception in client_connected_cb.

This is observed in production behind an HTTP/2 load balancer:

  File ".../hypercorn/protocol/h2.py", line 258, in _handle_events
    await self.streams[event.stream_id].handle(
KeyError: 1

The adjacent StreamEnded branch already guards the same lookup with a try/except KeyError for exactly this "response sent before full request received" case; the DataReceived branch was missing the equivalent guard.

Change

Wrap the DataReceived stream dispatch in try/except KeyError, mirroring the existing StreamEnded handling. Connection-level flow control is still acknowledged via acknowledge_received_data, since the stream remains valid at the h2 layer.

Added a regression test that reproduces the race (stream closed, then body data received) and asserts the connection no longer raises. Without the fix the test fails with KeyError: 1; with it the full tests/protocol/test_h2.py suite passes, and black, isort, flake8 and mypy are clean.

When an application sends its response and the stream is closed before
the client finishes sending the request body, h2 can still emit
DataReceived events for that stream. The DataReceived branch indexed
self.streams without guarding a missing key, raising KeyError and
crashing the connection. Mirror the existing StreamEnded guard and
ignore data for an already-closed stream.
@gonzageraci gonzageraci closed this by deleting the head repository Jul 10, 2026
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