Skip to content

Move HTTP/2 stream events cleanup inside state lock in _response_closed#1062

Open
bysiber wants to merge 1 commit intoencode:masterfrom
bysiber:fix-h2-response-closed-race-condition
Open

Move HTTP/2 stream events cleanup inside state lock in _response_closed#1062
bysiber wants to merge 1 commit intoencode:masterfrom
bysiber:fix-h2-response-closed-race-condition

Conversation

@bysiber
Copy link

@bysiber bysiber commented Feb 20, 2026

In _response_closed, the del self._events[stream_id] happens outside the _state_lock, but the not self._events check that decides whether to transition to IDLE happens inside the lock. This creates a race window with multiplexed HTTP/2 requests:

  1. Request A is the last active stream. _response_closed runs: releases semaphore, deletes events entry, yields before acquiring the lock
  2. Request B enters handle_async_request: acquires _state_lock, sets state=ACTIVE, releases lock
  3. Request B acquires semaphore and stream ID — but hasn't added self._events[stream_id] = [] yet
  4. Request A resumes: acquires _state_lock, sees not self._events is True (B hasn't added its entry), sets state=IDLE and _expire_at

Now the connection is marked IDLE with an expiry timer while Request B is actively using it. The connection pool may see the connection as expired and close it mid-request, or evict it as an excess idle connection.

Moving the del self._events[stream_id] inside the _state_lock ensures the events dict modification and the IDLE-transition check are atomic.

Applied to both async and sync implementations.

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

Comments