Repository navigation
[Triage] ASGI worker never enforces --keep-alive: idle connections are kept open indefinitely
#3734
mihaiblidaru
started this conversation in
Issue Triage
Replies: 1 comment
|
Opened #3735 with the fix and a regression test. |
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.
Type
Bug Report
Description
With the
asgiworker class, the--keep-alive(keepalive) setting has no effect: an idle keep-alive connection is never closed by the server. Thegthreadandgeventworkers, same gunicorn version and same setting, close the idle connection after the configured 2 seconds.Expected
The server closes the idle connection about 2 seconds after the response, so the second request (sent after 5 s idle) needs a new connection, as happens with the
gthreadandgeventworkers.Analysis
In
gunicorn/asgi/protocol.py,ASGIProtocol._handle_connectionarms the keep-alive timer at the end of a loop iteration and cancels it at the start of the next one. There is noawaitbetween the two, so the timer is always cancelled synchronously right after being armed, before the loop suspends in_request_ready.wait():_keepalive_timeout(which just calls_close_transport()) therefore never fires while a connection is idle between requests.cfg.keepaliveis still honoured as a boolean (if not self.cfg.keepalive: break), but not as a timeout.Steps to Reproduce (for bugs)
Dependencies:
pip install "gunicorn[fast]" quart flask gevent urllib3app_quart.pyapp_flask.pycheck_keepalive_urllib3.pyThe client sends two
GET /over one urllib3 connection pool with a 5 s idle pause in between, with urllib3 debug logging enabled. When the server has closed the pooled connection during the pause, urllib3 logsResetting dropped connectionbefore the second request; when the connection is still open it is reused silently, and the script exits with status 1.Run the
asgiworker with a 2 s keep-alive timeout and check:Control runs, same setting, WSGI workers:
Configuration
Logs / Error Output
Gunicorn Version
gunicorn (version 26.2.1), installed from the 26.2.1 tag tarball (gunicorn_h1c 0.6.9, uvloop 0.22.1)
Python Version
Python 3.12
Worker Class
asgi (beta)
Operating System
RHEL 8 (Linux 4.18)
Additional Context
Also in the environment: Quart 0.20.0 (ASGI app), Flask 3.1.3 (WSGI app for the control runs), urllib3 (client).
Suggested fix
Arm the timer before suspending in
_request_ready.wait(), only when the connection is idle between requests (not for the first request of a fresh connection), and cancel it once a request has arrived instead of unconditionally at the top of the loop. Waking the loop from_keepalive_timeoutlets it notice_closedand exit right away.Patch against 26.2.1, tested locally:
With this patch the reproduction steps above report
Resetting dropped connectionfor theasgiworker too, i.e. the idle connection is closed after 2 s. One quirk I found is what happens if the connection is opened but stays idle before the first request. With gthread and asgi, the request is served correctly. With the gevent worker, the connection is dropped by the server. With or without the proposed patch, ASGI behaves the same as the gthread worker: the request is served correctly.Impact
Idle client connections are only ever closed by the client. Nothing bounds the number of idle keep-alive connections a worker accumulates except the process file descriptor limit. Reproduced with a simulated abnormally low file descriptor limit (
ulimit -n 40): after 23 idle keep-alive connections the next client connection is reset, and since the idle ones never expire the worker does not recover on its own.Related
asgiworker ignoringkeepalivewith Django, but with the opposite symptom (idle connections closed immediately). It also states that a bare ASGI app kept the connection for the configured 30 s; that part does not reproduce here on 26.0.0 or 26.2.1, with either the asyncio or the uvloop event loop, the idle connection is never closed.nr_connsnot decremented by theasgiworker when the server closes a transport.asgiworker; todayworker_connectionsis not enforced by it. Current connection limit is set by the process file descriptor limit.masteras of 2026-09-06 (commit 5212b36) still arms and cancels the keepalive timer in the same order as 26.2.1, so the bug is not fixed there either.Checklist
All reactions