workers.md

July 14, 2026 · View on GitHub

Worker Modes

Pounce automatically selects the worker mode based on the Python runtime:

RuntimeWorkers AreDetection
Python 3.14t (free-threading)Threadssys._is_gil_enabled() returns False
Standard CPython (GIL)Processessys._is_gil_enabled() returns True

You don't need to configure this — the supervisor detects it at startup.

Configuring Worker Count

# Single worker (no supervisor, lowest overhead)
pounce serve --app myapp:app --workers 1

# Auto-detect from CPU cores
pounce serve --app myapp:app --workers 0

# Explicit count
pounce serve --app myapp:app --workers 4

Single Worker (workers=1)

The server skips the supervisor entirely and runs the worker directly in the main thread. This is the lowest-overhead mode — no supervisor thread, no health monitoring. Good for development and low-traffic services.

Auto-Detect (workers=0)

Calls os.cpu_count() and uses that as the worker count (minimum 1). On an 8-core machine, this creates 8 workers.

Multi-Worker (workers=2+)

The supervisor spawns N workers and monitors their health. Workers that crash are automatically restarted (max 5 restarts per 60-second window).

Thread vs Process Workers

flowchart LR
    subgraph ft ["Free-Threading (3.14t)"]
        direction TB
        P1["1 Process"] --> T1["Thread 1\nevent loop"]
        P1 --> T2["Thread 2\nevent loop"]
        P1 --> TN["Thread N\nevent loop"]
        T1 ~~~ Mem1["Shared Memory\nconfig, app, routes"]
        T2 ~~~ Mem1
        TN ~~~ Mem1
    end

    subgraph gil ["GIL Build"]
        direction TB
        Sup["Supervisor"] --> PR1["Process 1\nevent loop"]
        Sup --> PR2["Process 2\nevent loop"]
        Sup --> PRN["Process N\nevent loop"]
        PR1 ~~~ Mem2["Isolated Memory ×N"]
    end

Thread Workers (Free-Threading)

On Python 3.14t, each worker is a thread with its own asyncio event loop:

  • Shared memory — All workers see the same ServerConfig, app reference, and route tables
  • No IPC — Threads communicate through shared memory, not pipes
  • Lower memory — One copy of the application, not N copies
  • SO_REUSEPORT — Kernel distributes incoming connections across workers

Process Workers (GIL)

On GIL builds, each worker is a separate process:

  • Isolated memory — Each process has its own copy of everything
  • Fork-based — The already-imported app and completed main lifespan startup are inherited
  • Higher memory — N copies of the application

:::{warning} The state inherited by process workers must be fork-safe. Do not start background threads or executors, hold synchronization primitives, or create process-local clients during module import, Server construction, or lifespan.startup when using multiple workers on a GIL build. A child can inherit a locked object without the thread that would release it, report ready, and then hang when a request touches that object. :::

Pounce deliberately uses fork so closures and other non-picklable ASGI callables continue to work. It does not currently provide a spawn or forkserver process-worker option.

Create or replace process-local resources in pounce.worker.startup, which runs separately inside every worker before it accepts requests. Set worker_startup_failure="shutdown" when that initialization must succeed before readiness. If the app cannot satisfy this pre-fork contract, use --workers 1 or deploy on Python 3.14t, where automatic multi-worker mode uses threads instead of forked processes.

Tuning Guidelines

WorkloadRecommended Workers
CPU-bound (computation)os.cpu_count() (workers=0)
I/O-bound (database, network)os.cpu_count() * 2
Development1 (single worker)
Mixedos.cpu_count() (start here, tune)

:::{tip} Start with --workers 0 (auto-detect) and monitor. Adjust based on actual CPU utilization and latency metrics. :::

Health Monitoring

The supervisor runs a health-check loop for multi-worker mode:

  • Workers that crash are restarted automatically
  • Maximum 5 restarts per 60-second window (prevents restart loops)
  • Graceful shutdown signals all workers before exit

See Also

  • [[docs/about/thread-safety|Thread Safety]] — Shared state model
  • [[docs/deployment/embedding|Framework Embedding]] — Launcher and initialization contracts
  • [[docs/about/architecture|Architecture]] — Supervisor design
  • [[docs/deployment/production|Production]] — Full production setup