Discussion: #814
The pool timeout exception is now only raised when we are waiting for a connection from a pool and the pool is always full.
Problem
When the response releases the connection, all the requests in the queue can take the same connection.
But in the AsyncHTTP11Connection class, when the handle_async_request is called, it checks if his state is correct. If the state is not NEW or IDLE, that means that the other request has stolen that connection, and it raises ConnectionNotAvailable.
Like so:
| asyncwithself._state_lock: |
| ifself._statein (HTTPConnectionState.NEW, HTTPConnectionState.IDLE): |
| self._request_count+=1 |
| self._state=HTTPConnectionState.ACTIVE |
| self._expire_at=None |
| else: |
| raiseConnectionNotAvailable() |
Because the queued request has this cycle, the program may hang indefinitely.
- waiting for a connection
- gets the connection that is already in use (but the connection state has not been changed yet, so it thinks he is the only one who uses that connection)
- catches the
ConnectionNotAvailable exception and tries again with the same pool timeout.
There is a code that can reproduce this issue (credits to @valsteen).
Server
importasynciofromfastapiimportFastAPIapp=FastAPI()
@app.get("/")asyncdefroot():
awaitasyncio.sleep(2)Client
importtimeimporttriofromhttpcoreimportAsyncConnectionPoolTIMEOUTS= {"read": 5, "write": 5, "pool": 5, "connect": 5}
asyncdefread():
start=time.time()
try:
response=awaithttp.request(
"GET", "http://127.0.0.1:8000", extensions={"timeout": TIMEOUTS}
)
ifresponse.status//100!=2:
raiseRuntimeErrorexceptExceptionasex:
print(f"failed: {ex.__class__}")
print(f"time: {time.time()-start}")
asyncdefmain():
asyncwithtrio.open_nursery() asnursery:
for_inrange(100):
nursery.start_soon(read)
if__name__=="__main__":
http=AsyncConnectionPool(
max_connections=3,
max_keepalive_connections=3,
)
trio.run(main)
Discussion: #814
The pool timeout exception is now only raised when we are waiting for a connection from a pool and the pool is always full.
Problem
When the response releases the connection, all the requests in the queue can take the same connection.
But in the AsyncHTTP11Connection class, when the
handle_async_requestis called, it checks if his state is correct. If the state is not NEW or IDLE, that means that the other request has stolen that connection, and it raises ConnectionNotAvailable.Like so:
httpcore/httpcore/_async/http11.py
Lines 78 to 84 in 8780c9c
Because the queued request has this cycle, the program may hang indefinitely.
ConnectionNotAvailableexception and tries again with the same pool timeout.There is a code that can reproduce this issue (credits to @valsteen).
Server
Client