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 also seems to create a deadlock.
It can be reproduced with the following code:
importtrioimporthttpcoreTIMEOUT= {"read": 5, "write": 5, "connect": 5, "pool": 5}
asyncdefsend_request(client, seen_response):
response=awaitclient.request("GET", "http://127.0.0.1:8000", extensions={"timeout": TIMEOUT})
# Print the first response that we see and set the "seen_response" flag.ifnotseen_response.is_set():
print(response)
seen_response.set()
asyncdefmain():
asyncwithhttpcore.AsyncConnectionPool() asclient:
while1:
asyncwithtrio.open_nursery() asnursery:
# This event is used to signal the first response we recieve.seen_response=trio.Event()
# Kick off one hundred HTTP requests.foridxinrange(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.awaitseen_response.wait()
nursery.cancel_scope.cancel()
trio.run(main)This should produce a httpcore.PoolTimeout error on the second or third iteration, after which the loop enters into a deadlock.
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 also seems to create a deadlock.
It can be reproduced with the following code:
This should produce a
httpcore.PoolTimeouterror on the second or third iteration, after which the loop enters into a deadlock.