Module

async_pool

AsyncPool — dedicated event loop for streaming and WebSocket connections.

Receives handoffs from SyncWorkers when the ASGI app returns a streaming response (more_body=True) or WebSocket upgrade. Wraps the socket in asyncio streams and continues the ASGI lifecycle.

Classes

StreamingHandoff 6 ▼
Handoff for HTTP streaming response (more_body=True).

Handoff for HTTP streaming response (more_body=True).

Attributes

Name Type Description
conn socket.socket —
scope dict[str, Any] —
body bytes —
request_id str | None —
worker_id int —
connection_id int —
WebSocketHandoff 5 ▼
Handoff for WebSocket upgrade.

Handoff for WebSocket upgrade.

Attributes

Name Type Description
conn socket.socket —
request RequestReceived —
client tuple[str, int] —
server tuple[str, int] —
scope dict[str, Any] —
AsyncPool 13 ▼
Dedicated event loop for streaming/SSE/WebSocket connections. Accepts handoffs from SyncWorker thr…

Dedicated event loop for streaming/SSE/WebSocket connections.

Accepts handoffs from SyncWorker threads via a thread-safe queue. Wraps sockets in asyncio streams and runs the ASGI app.

Methods

set_lifespan_state 1 ▼
Set the lifespan state dict shared with all requests.
def set_lifespan_state(self, state: dict[str, Any]) -> None
Parameters
Name Type Description
state —
set_app 1 ▼
Swap the ASGI app used for future handoffs. Safe to call while the pool is run…
def set_app(self, app: ASGIApp) -> None

Swap the ASGI app used for future handoffs.

Safe to call while the pool is running:self._appis read once per handoff (in _handle_streaming_handoff / _handle_websocket_handoff), not captured at loop start. Used by graceful reload to point a freshly built pool at the reimported app (issue #102).

Parameters
Name Type Description
app —
request_shutdown 0 ▼
Signal this pool's serve loop to stop accepting new handoffs. Distinct from th…
def request_shutdown(self) -> None

Signal this pool's serve loop to stop accepting new handoffs.

Distinct from the shared_ext_shutdown: retires only this pool so a graceful reload can drain the old generation without tearing down the whole server (issue #102).

accept_handoff 1 ▼
Accept a handoff from a SyncWorker (thread-safe).
def accept_handoff(self, handoff: HandoffRequest) -> None
Parameters
Name Type Description
handoff —
run 0 ▼
Run the event loop until shutdown (blocking).
def run(self) -> None
Internal Methods 8 ▼
__init__ 5 ▼
def __init__(self, config: ServerConfig, app: ASGIApp, *, shutdown_event: threading.Event | None = None, ssl_context: ssl.SSLContext | None = None, lifecycle_collector: LifecycleCollector | None = None) -> None
Parameters
Name Type Description
config —
app —
shutdown_event — Default:None
ssl_context — Default:None
lifecycle_collector — Default:None
_serve 0 ▼
Event loop: process handoffs until shutdown. The pool retires on its OWN ``_po…
async
async def _serve(self) -> None

Event loop: process handoffs until shutdown.

The pool retires on its OWN_pool_shutdownevent, NOT on the shared _ext_shutdown(#104). On a FULL shutdown the supervisor sets _ext_shutdown immediately but only calls request_shutdown() (which sets_pool_shutdown) AFTER the sync workers have drained — because a draining worker can still be SERVING an in-flight streaming/WebSocket request that it hands off to this pool. Retiring on _ext_shutdownwould close the pool before that late handoff arrives, orphaning the connection (empty response). A safety deadline keyed off _ext_shutdownguarantees the pool still self-retires even if request_shutdown() is never delivered (e.g. a supervisor fault).

_start_handoff 1 ▼
Schedule a handoff as a tracked task.
def _start_handoff(self, handoff: HandoffRequest) -> None
Parameters
Name Type Description
handoff —
_drain_queued_handoffs 1 ▼
Pull and start any handoffs that land during the drain window. On a FULL shutd…
async
async def _drain_queued_handoffs(self, timeout: float) -> None

Pull and start any handoffs that land during the drain window.

On a FULL shutdown the sync workers retire concurrently and hand off their last in-flight streaming/WebSocket requests to this pool. The pool may observe shutdown and exit its main loop before those handoffs are enqueued, so keep polling the queue here until it has stayed empty AND no handoff task is in flight for a sustained idle interval (the workers have finished enqueuing). Bounded by timeout so this can never spin forever; in-flight tasks themselves are awaited by _drain_handoffs.

Parameters
Name Type Description
timeout —
_drain_handoffs 1 ▼
Await in-flight handoff tasks up to *timeout*, then cancel stragglers.
async
async def _drain_handoffs(self, timeout: float) -> None
Parameters
Name Type Description
timeout —
_handle_handoff_async 1 ▼
Handle a handoff (async task).
async
async def _handle_handoff_async(self, handoff: HandoffRequest) -> None
Parameters
Name Type Description
handoff —
_handle_streaming_handoff 1 ▼
Handle HTTP streaming handoff: wrap socket, run app from scratch.
async
async def _handle_streaming_handoff(self, handoff: StreamingHandoff) -> None
Parameters
Name Type Description
handoff —
_handle_websocket_handoff 1 ▼
Handle WebSocket handoff: wrap socket, run handle_websocket.
async
async def _handle_websocket_handoff(self, handoff: WebSocketHandoff) -> None
Parameters
Name Type Description
handoff —

Functions

_create_h1_protocol 1 H1Protocol ▼
Create an HTTP/1.1 protocol handler.
def _create_h1_protocol(*, max_incomplete_event_size: int | None = None) -> H1Protocol
Parameters
Name Type Description
max_incomplete_event_size int | None Default:None
Returns
H1Protocol