Race Condition with Asyncio Cancelled Tasks #783
Replies: 2 comments 7 replies
-
It's strange because at the end of the program, the connection pool is full of closed connections; these connections were supposed to be removed from the pool, but that doesn't happen. |
Beta Was this translation helpful? Give feedback.
-
@tomchristie thanks for taking the time to look into this. I'll admit this isn't the first cancellation-related race condition we've run into with asyncio, so I'm not opposed to a trio migration, although I just finished converting our synchronous codebase to asyncio, so that particular idea causes me some amount of weariness. 🫠 (if you felt the same way about this very issue, I wouldn't blame you). The examples you provided indeed do not reveal the bug. But I was able to reproduce it in trio by iterating again with the same httpcore client, like so: import trio
import httpcore
TIMEOUT = {"read": 5, "write": 5, "connect": 5, "pool": 5}
async def send_request(client, seen_response):
response = await client.request("GET", "http://127.0.0.1:8000", extensions={"timeout": TIMEOUT})
# Print the first response that we see and set the "seen_response" flag.
if not seen_response.is_set():
print(response)
seen_response.set()
async def main():
async with httpcore.AsyncConnectionPool() as client:
while 1: # <-----------------
async with trio.open_nursery() as nursery:
# This event is used to signal the first response we recieve.
seen_response = trio.Event()
# Kick off one hundred HTTP requests.
for idx in range(100):
nursery.start_soon(send_request, client, seen_response)
# As soon as we see a response we can cancel out of the nursery,
# rather than allowing all tasks to complete.
await seen_response.wait()
nursery.cancel_scope.cancel()
trio.run(main) For me, this reproduces the Let me know what you think. |
Beta Was this translation helpful? Give feedback.
-
Hello, I was directed to open a discussion here based on this one over at httpx.
The gist of the race condition is that when a web request is cancelled within a specific time window, it will degrade the state of the connection pool, leaving it unable to issue further requests.
It can be reproduced with the following code:
In essence, this code will create a batch of web requests, get the first successful result, then cancel the rest, rinse and repeat.
On my machine it runs for about ten to twenty iterations, hangs for the specified timeout (5 seconds), then throws an httpx.PoolTimeout error and fails to recover.
Thanks!
Beta Was this translation helpful? Give feedback.
All reactions