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
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
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