# Workers

URL: /pounce/docs/deployment/workers/
Section: deployment
Description: Configuring worker count and understanding thread vs process mode

---

> For a complete page index, fetch /pounce/llms.txt.

## Worker Modes

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

| Runtime | Workers Are | Detection |
|---------|-----------|-----------|
| Python 3.14t (free-threading) | Threads | `sys._is_gil_enabled()` returns `False` |
| Standard CPython (GIL) | Processes | `sys._is_gil_enabled()` returns `True` |

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

## Configuring Worker Count

```bash
# 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

```mermaid
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

| Workload | Recommended Workers |
|----------|-------------------|
| CPU-bound (computation) | `os.cpu_count()` (workers=0) |
| I/O-bound (database, network) | `os.cpu_count() * 2` |
| Development | `1` (single worker) |
| Mixed | `os.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
