AsyncConnectionPool gets stuck upon task group cancellation #971
Unanswered
juneoh
asked this question in
Potential Issue
Replies: 2 comments 1 reply
-
Partial reproduction for case 1: import functools
import unittest.mock
import httpcore
import trio
async def main():
pool = httpcore.AsyncConnectionPool(max_connections=1)
request = functools.partial(
pool.request,
"GET",
"https://www.example.com/",
extensions={"timeout": {"pool": 10}},
)
async def side_effect(*args, **kwargs):
print(pool)
await trio.sleep(1)
raise trio.Cancelled._create()
with unittest.mock.patch(
"httpcore._async.connection_pool.AsyncPoolRequest.wait_for_connection",
side_effect,
):
async with trio.open_nursery() as nursery:
nursery.start_soon(request)
nursery.start_soon(request)
print(pool)
print(pool._connections[0].info())
await request()
trio.run(main) |
Beta Was this translation helpful? Give feedback.
1 reply
-
Reproduction for case 2: import functools
import unittest.mock
import httpcore
import trio
class MockContextManager:
async def __aenter__(self):
await trio.sleep(1)
raise trio.Cancelled._create()
async def __aexit__(self):
pass
class MockAsyncHTTP11Connection(httpcore.AsyncHTTP11Connection):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._state_lock = MockContextManager()
async def main():
pool = httpcore.AsyncConnectionPool(max_connections=1)
request = functools.partial(
pool.request,
"GET",
"https://www.example.com/",
extensions={"timeout": {"pool": 10}},
)
with unittest.mock.patch(
"httpcore._async.connection.AsyncHTTP11Connection",
MockAsyncHTTP11Connection,
):
async with trio.open_nursery() as nursery:
nursery.start_soon(request)
nursery.start_soon(request)
print(pool)
print(pool._connections[0].info())
await request()
trio.run(main) Please forgive the mocks, since it's very difficult to time task cancellations. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
PoolTimeout
is raised with 0 active requests, but 5 active connections. I.e.,Case 1: Active connection's
AsyncHTTPConnection.info()
returnsCONNECTING
<AsyncConnectionPool [Requests: 5 active, 251 queued | Connections: 5 active, 0 idle]>
where all the requests belong to the same anyio task group.trio.Cancelled
is caught fromAsyncConnectionPool.handle_async_request()
AsyncConnectionPool._assign_requests_to_connections()
creates a new connection inCONNECTING
state, and assigns it to a pending request.connection = await pool_request.wait_for_connection(timeout=timeout)
also raisestrio.Cancelled
.CONNECTING
connection is removed, but the connection remains.AsyncHTTPConnection.is_available()
(assuming HTTP/1)Case 2: Active connection's
AsyncHTTP11Connection.info()
returns[..] NEW [..]
trio.Cancelled
arises fromasync with self._state_lock
withinAsyncHTTP11Connection.handle_async_request()
.AsyncConnectionPool.handle_async_request()
.AsyncConnectionPool._assign_requests_to_connections()
does not remove the connection, because it isNEW
.IDLE
as mandated byAsyncHTTP11Connection.is_available()
.Traceback (the line numbers may differ due to my additional logging):
Beta Was this translation helpful? Give feedback.
All reactions