# Architecture URL: /docs/about/architecture/ Section: about Tags: architecture, internals, design -------------------------------------------------------------------------------- Overview Pounce follows a three-layer architecture: Server orchestrates lifecycle, Supervisor manages workers, and Workers handle requests. All layers share a single frozen ServerConfig — no synchronization needed. flowchart TD Server["Server\nCONFIG → BIND → SERVE"] Supervisor["Supervisor\ndetect nogil → threads\ndetect GIL → processes"] Server --> Supervisor Supervisor -- "spawn N workers" --> W1["Worker 1\nasyncio event loop"] Supervisor -- "spawn N workers" --> W2["Worker 2\nasyncio event loop"] Supervisor -- "spawn N workers" --> WN["Worker N\nasyncio event loop"] W1 --> Shared["Shared Immutable State\n(config, app reference)"] W2 --> Shared WN --> Shared Server Layer The Server class orchestrates the full lifecycle: CONFIG — Validate and freeze ServerConfig DETECT — Check for free-threading via sys._is_gil_enabled() BIND — Create listening sockets with SO_REUSEPORT LIFESPAN — Run ASGI lifespan protocol (startup / shutdown) SERVE — Delegate to single-worker fast path or multi-worker supervisor SHUTDOWN — Graceful connection draining, signal handling For single-worker mode (workers=1), the server skips the supervisor entirely and runs the worker directly — no thread/process overhead. Supervisor Layer The Supervisor manages worker lifecycle: Spawn — Creates N worker threads (nogil) or processes (GIL) Monitor — Health-check loop with automatic restart (max 5 restarts per 60s window) Reload — Graceful restart of all workers when --reload detects changes Shutdown — Signals all workers to stop, waits for connection draining Worker Layer Each Worker runs its own asyncio event loop: Accept — Receive TCP connections from the shared socket Parse — Feed bytes to protocol parser (h11, h2, or wsproto) Bridge — Build ASGI scope, create receive/send callables Dispatch — Call app(scope, receive, send) Respond — Serialize response and write to socket (streaming) Workers are fully independent. No shared mutable state, no locks, no coordination between workers during request handling. Request Pipeline A single HTTP request flows through: flowchart LR A[Socket Accept] --> B[TLS Unwrap] B --> C{Protocol\nDetection} C -->|h1| D1[h11 / httptools] C -->|h2| D2[h2] D1 --> E[ASGI Scope\nConstruction] D2 --> E E --> E2[Proxy Header\nValidation] E2 --> E3[Request ID\nGeneration] E3 --> E4{Health\nCheck?} E4 -->|yes| E5[Health Response] E4 -->|no| F["app(scope, receive, send)"] F --> G[Response\nSerialization] G --> G2[CRLF\nSanitization] G2 --> H[Compression\nzstd / gzip / identity] H --> I[Server-Timing\nHeader Injection] I --> J[Socket Write] The bridge is per-request — created and destroyed within a single connection handler. This ensures zero cross-request state leakage. Module Map Module Layer Purpose server.py Server Lifecycle orchestration supervisor.py Supervisor Worker spawn/monitor worker.py Worker asyncio loop, request handling config.py Shared Frozen ServerConfig protocols/h1.py Protocol HTTP/1.1 via h11 protocols/h1_httptools.py Protocol HTTP/1.1 via httptools protocols/h2.py Protocol HTTP/2 via h2 protocols/ws.py Protocol WebSocket via wsproto asgi/bridge.py Bridge HTTP ASGI scope/receive/send asgi/h2_bridge.py Bridge HTTP/2 ASGI bridge asgi/ws_bridge.py Bridge WebSocket ASGI bridge asgi/lifespan.py Bridge ASGI lifespan protocol net/listener.py Network Socket bind, SO_REUSEPORT, UDS net/tls.py Network TLS context creation _proxy.py Security Proxy header validation _request_id.py Observability Request ID generation/extraction _health.py Observability Built-in health check endpoint metrics.py Observability Prometheus-compatible metrics See Also Thread Safety — How shared state works Performance — Streaming-first design Protocols — Protocol handler details -------------------------------------------------------------------------------- Metadata: - Word Count: 488 - Reading Time: 2 minutes