Graceful shutdown¶
On SIGTERM or SIGINT the binary does what uvicorn does: it stops accepting connections, closes idle
keep-alive connections, lets in-flight requests finish (their response carries connection: close), closes
WebSockets with code 1012, runs the lifespan's shutdown code, closes the database pool and exits with the
signal's status (143 for SIGTERM). A second signal exits at once.
The one difference: the wait is bounded by PY2AXUM_SHUTDOWN_TIMEOUT (25 s by default), where uvicorn waits
without limit, so an endless stream (an SSE feed) no longer holds the process until the orchestrator's
SIGKILL. Keep it below your orchestrator's grace period (Kubernetes: terminationGracePeriodSeconds, 30 s by
default).
Details¶
Measured against uvicorn 0.54 (both servers, same clients), for every generated binary, with or without a lifespan:
- As uvicorn: the listening socket is closed at once (new connections are refused), idle keep-alive
connections are closed, in-flight requests finish and their response carries
connection: close, WebSocket sessions get a close frame 1012 and the application receiveswebsocket.disconnectwith code 1012, then the lifespan's code afteryieldruns, and the process ends by the signal it received (exit status 143 for SIGTERM, uvicorn re-raises it). A second signal skips the wait (uvicorn's force exit). - Differences: the wait for in-flight requests and streams is bounded by
PY2AXUM_SHUTDOWN_TIMEOUTseconds (default 25; uvicorn waits without a limit unless--timeout-graceful-shutdown, so an endlessStreamingResponsesuch as an SSE feed holds it until the orchestrator's SIGKILL), after which the remaining connections are dropped; the database pool is closed before the exit (PostgreSQL gets a Terminate message; the Python process leaves its connections to be dropped with it).
The lifespan itself is described in Lifespan events; Sentry's queue is flushed at shutdown (Sentry).