You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In httpcore2 2.12.0, cancellation during HTTP/1.1 response cleanup can leave the connection pool in an inconsistent state:
the pool request remains registered without a live caller;
the established HTTP/1.1 connection remains ACTIVE; and
retrying response.aclose() does not resume the interrupted cleanup.
The retained connection continues to count toward max_connections. With max_connections=1, the next request fails with PoolTimeout.
This appears distinct from [#982](#982) and [#983](#983). PR #983 handles cancellation before connection establishment, where an unreferenced NEW connection remains in the pool. This reproducer cancels cleanup after an HTTP/1.1 connection has been established and a response has been returned.
Reproducer
The following standalone script uses AsyncMockBackend, so it does not require a web framework, external server, or network access.
PR #983 handles a connection cancelled before establishment:
request removed
connection establishment incomplete
connection.is_connected() == False
unreferenced NEW connection remains in pool
This reproducer reaches a later lifecycle stage before cancellation:
HTTP/1.1 connection established
connection.is_connected() == True
response returned to the caller
connection state == ACTIVE
response cleanup started
After cancellation, the connection is still referenced by the retained pool request. Therefore, the garbage-connection condition introduced by #983 does not remove it:
Please make established HTTP/1.1 response cleanup cancellation-safe and ensure that pool bookkeeping completes even if underlying stream cleanup is cancelled.
Potentially relevant paths include:
PoolByteStream.aclose()
HTTP11ConnectionByteStream.aclose()
AsyncHTTP11Connection._response_closed()
AsyncHTTP11Connection.aclose()
The desired invariant is:
Once a response has ended or its cancellation has propagated, its pool request must not remain retained without a live response, and the associated connection must be reusable (IDLE) or terminal (CLOSED), never permanently ACTIVE.
I can prepare a PR with a regression test and proposed fix if this diagnosis and expected behavior look correct.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Summary
In
httpcore2 2.12.0, cancellation during HTTP/1.1 response cleanup can leave the connection pool in an inconsistent state:ACTIVE; andresponse.aclose()does not resume the interrupted cleanup.The retained connection continues to count toward
max_connections. Withmax_connections=1, the next request fails withPoolTimeout.This appears distinct from [#982](#982) and [#983](#983). PR #983 handles cancellation before connection establishment, where an unreferenced
NEWconnection remains in the pool. This reproducer cancels cleanup after an HTTP/1.1 connection has been established and a response has been returned.Reproducer
The following standalone script uses
AsyncMockBackend, so it does not require a web framework, external server, or network access.Actual output
Expected behavior
After response cleanup completes or cancellation propagates:
A cancelled response close should not leave an unusable
ACTIVEconnection occupying a pool slot indefinitely.Observed control flow
The failure appears to occur in this sequence:
PoolByteStream.aclose()marks the outer pool stream as closed.HTTP11ConnectionByteStream.aclose().AsyncHTTP11Connection._response_closed()waits for_state_lock.IDLEorCLOSED.PoolByteStream.aclose()bookkeeping, so the pool request is not removed.response.aclose()is a no-op because the outer stream is already marked closed.This leaves the following retained state:
Difference from #982 / #983
PR #983 handles a connection cancelled before establishment:
This reproducer reaches a later lifecycle stage before cancellation:
After cancellation, the connection is still referenced by the retained pool request. Therefore, the garbage-connection condition introduced by #983 does not remove it:
In this case:
Requested behavior
Please make established HTTP/1.1 response cleanup cancellation-safe and ensure that pool bookkeeping completes even if underlying stream cleanup is cancelled.
Potentially relevant paths include:
PoolByteStream.aclose()HTTP11ConnectionByteStream.aclose()AsyncHTTP11Connection._response_closed()AsyncHTTP11Connection.aclose()The desired invariant is:
I can prepare a PR with a regression test and proposed fix if this diagnosis and expected behavior look correct.
Environment
3.11.4httpcore2:2.12.0AsyncMockBackendAll reactions