Workers & Lifecycle

Because Fast keeps workers alive across many requests, it needs a strategy to stop small problems, a slow memory creep, a leaked resource, from accumulating forever. That strategy is worker recycling, and paired with the start and shutdown hooks, it gives you a clean, self-healing process model.

Why recycle at all

A persistent PHP process is wonderful for speed but risky for longevity. Even well-behaved code can slowly grow memory through fragmentation or third-party quirks. Rather than chase every last byte, Fast periodically retires a worker and forks a fresh one. The new worker starts from a clean slate, so any slow accumulation resets.

The recycling knobs

You control recycling through serve() arguments, which map to the server Config:

OptionDefaultMeaning
maxRequests1000Recycle a worker after this many requests (0 = off).
maxMemory128 MiBRecycle a worker once it exceeds this real memory (0 = off).
idleTimeout15.0Seconds before an idle keep-alive connection is closed.
gcInterval50Run gc_collect_cycles() every N requests (0 = off).

A worker that hits either the request count or the memory ceiling finishes its current request, then drains and is replaced by a fresh fork. Clients never see this; it happens between requests.

serve(
	port: 8080,
	workers: 4,
	maxRequests: 2000,
	maxMemory: 192 * 1024 * 1024,
);

A note for database-heavy apps

Recycling re-forks the worker, which means it also reconnects that worker's whole database executor pool. If you recycle very aggressively while using the database, you pay reconnect churn:

When you use the database layer, prefer a higher maxRequests, or lean on memory-based recycling instead of request-count recycling, to avoid constantly tearing down and rebuilding executor connections. The goal is to recycle often enough to stay healthy, but not so often that reconnecting dominates.

Startup and shutdown hooks

Two hooks let you run code at the edges of a worker's life. on_start fires once when a worker boots, right after the app is built, and on_shutdown fires once as it drains:

on_start(function (): void {
	// Warm a cache, open a per-worker resource, log the boot.
});

on_shutdown(function (): void {
	// Flush buffers, close a resource, log the drain.
});

Both run inside the worker and off the request path. Because a recycled worker runs on_start again when its replacement boots, these hooks are the right place for per-worker setup rather than one-time global setup.

Graceful shutdown

When you stop the server with Ctrl-C, Fast does not just kill everything. The master signals its workers to drain: they stop accepting new connections, finish the requests already in flight, cancel long-lived streams cooperatively, run their on_shutdown hooks, and then exit. This is what lets you deploy or restart without dropping requests mid-flight.

Long-lived connections get special care here. SSE and WebSocket fibers are cancelled cooperatively during a drain, so a streaming worker still shuts down cleanly instead of hanging on an open connection.

Next, the details of what runs where across operating systems, in Platform Support.