The Free-Threading Model
Python 3.14t (PEP 703) removes the Global Interpreter Lock. For the first time, Python threads execute in true parallel. This changes the rules for server design:
- GIL builds: Multi-worker means multi-process (fork). Each process has its own memory space. Thread safety is irrelevant between workers.
- Free-threading builds: Multi-worker means multi-thread. All workers share one memory space. Thread safety matters.
Pounce is designed for the free-threading world. The key principle: shared data is immutable, mutable data is per-request.
What's Shared (Immutable)
These are shared across all worker threads with zero synchronization:
| Data | Type | Why It's Safe |
|---|---|---|
ServerConfig |
@dataclass(frozen=True) |
Immutable after creation |
| Application reference | Function/class | Read-only reference |
| Socket objects | OS-managed | Kernel handles concurrent accept |
| Protocol constants | Module-levelfrozenset/tuple |
Immutable collections |
ServerConfig uses frozen=True and slots=True — any attempt to mutate it raises FrozenInstanceError.
What's Per-Request (Mutable)
These are created fresh for each request and never shared:
| Data | Scope | Lifecycle |
|---|---|---|
| ASGI scope dict | Per-request | Created at parse, GC'd after response |
receivecallable |
Per-request | Bound to connection reader |
sendcallable |
Per-request | Bound to connection writer |
| Protocol parser state | Per-connection | Reset between requests (keep-alive) |
| Compression state | Per-response | Created per-response |
| Server-Timing metrics | Per-request | Injected into response headers |
What's Per-Worker
Each worker has its own:
- asyncio event loop — Event loops are not thread-safe; each worker runs its own
- Connection tracking — Per-worker connection count for backpressure
- Logging context — Worker ID tagged in log output
Auto-Detection
Pounce detects the runtime mode at startup viasys._is_gil_enabled():
# On Python 3.14t (free-threading):
# GIL disabled → workers are threads
# On GIL builds (standard CPython):
# GIL enabled → workers are processes
The supervisor adapts automatically. Your code and config stay the same.
Guidelines for ASGI Apps
Pounce handles thread safety for its own internals. For your ASGI application:
Tip
If your app works correctly with Uvicorn's multi-process mode, it will work with Pounce's thread mode — the isolation model is the same (per-request scope, no shared mutable state in the framework bridge).
Warning
If your app uses global mutable state (module-level dicts, caches without locks), you'll need to add synchronization for free-threading builds. This is a Python-wide concern, not Pounce-specific.
See Also
- Architecture — Server layer design
- Comparison — How other servers handle threading
- Workers — Configuring worker count and mode