Skip to content

fix: decrement nr_conns on server-initiated close in the ASGI worker (#3661) - #3686

Merged
benoitc merged 3 commits into
masterfrom
fix/asgi-nr-conns-server-close-3661
Aug 16, 2026
Merged

fix: decrement nr_conns on server-initiated close in the ASGI worker (#3661)#3686
benoitc merged 3 commits into
masterfrom
fix/asgi-nr-conns-server-close-3661

Conversation

@benoitc

@benoitc benoitc commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Supersedes #3664, whose commit is carried over unchanged and still authored by @apoorvdarshan. Fixes #3661.

nr_conns is only decremented in connection_lost(), behind an idempotence guard keyed on _closed. _close_transport() sets _closed before asyncio delivers the transport loss, so every close the server starts returns at the guard and leaks one count for good. That covers Connection: close responses, keepalive timeouts and error aborts. Client-initiated closes stay balanced because connection_lost() runs first.

The early return also skips the rest of the cleanup: the keepalive timer, reader.feed_eof() and the body receiver's signal_disconnect(), so the app is never told the peer has gone (#3484).

ASGIWorker._shutdown() polls nr_conns against a deadline, so a leaked count makes every graceful stop run the full graceful_timeout and then warn about connections that are already gone.

The guard now uses its own flag, so the decrement and the rest of the cleanup run exactly once whichever side closes first.

The ASGI protocol shared one _closed flag for two purposes: marking the
transport closed (_close_transport) and guarding the connection_lost()
cleanup. On a server-initiated close (Connection: close, keepalive timeout,
etc.) _close_transport() set _closed=True before asyncio reported the
transport loss, so connection_lost() returned early and never decremented
worker.nr_conns. The count grew with worker age and every graceful stop
waited out the full graceful_timeout.

Use a dedicated _conn_lost_handled flag for the connection_lost() idempotency
guard so its cleanup (nr_conns decrement, disconnect signalling) always runs
exactly once regardless of who initiated the close.

Fixes #3661.
@benoitc
benoitc force-pushed the fix/asgi-nr-conns-server-close-3661 branch from a84d93f to e8a9f86 Compare August 16, 2026 09:37
@benoitc
benoitc merged commit bfbae10 into master Aug 16, 2026
26 checks passed
@benoitc
benoitc deleted the fix/asgi-nr-conns-server-close-3661 branch August 16, 2026 09:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ASGI worker: nr_conns never decremented on server-initiated close — every graceful stop waits out the full graceful_timeout

2 participants