I encountered a race condition where uvicorn closes the connection at random. The problem seems to be incorrect management of the keep-alive timer in uvicorn/protocols/http/httptools_impl.py (HttpToolsProtocol).
Under some conditions, on_response_complete might be called after data has been received for the next request. When this happens, there might be two on_response_complete calls without a data_received call inbetween, hence timeout_keep_alive_task is overridden with a new timer without canceling the old one. When the leaked timer expires, the connection is killed.
Server side:
import asyncio
async def app(scope, receive, send):
m = await receive()
if m['type'] == 'lifespan.startup':
await send({'type': 'lifespan.startup.complete'})
elif m['type'] == 'http.request':
await asyncio.sleep(.2)
await send({'type': 'http.response.start', 'status': 200, 'headers': [(b'content-length',b'5')]})
await send({'type': 'http.response.body', 'body': b'data\n', 'more_body': True})
await asyncio.sleep(.5)
await send({'type': 'http.response.body', 'body': b'', 'more_body': False})
Client side:
import requests
import time
s = requests.Session()
for i in range(2):
s.get(f'http://localhost:8000/{i}')
time.sleep(1)
for i in range(10):
s.get(f'http://localhost:8000/{i}')
@itayperl
The bug is reproducible using your snippet.
Probably yes, the bug related to the fact, that setting new keep-alive task, we do not cancel the old-one
Probably I found the solution. Once I write test cases, I'll fire a PR
As you can see the tests are failing without the fix
https://github.com/encode/uvicorn/pull/831/checks?check_run_id=1298976600
Most helpful comment
Probably I found the solution. Once I write test cases, I'll fire a PR