2: The worker pool

The daemon keeps a pool of resident PHP workers per app. Each worker boots the app once and then answers many calls, removing the per-call boot that the one-shot path pays. It is the same idea as FrankenPHP's HTTP worker mode, but for non-HTTP work.

2.1: The worker protocol

A worker runs php <app.php> phlo_serve, boots the app, and then reads newline-delimited JSON on stdin, one request in flight at a time:

in   {"id", "target", "args"?, "stream"?}
out  {"t":"ready"}                                            // once, after boot
     {"id", "t":"line", "data"}                               // 0..N, only when streaming
     {"id", "t":"done", "result"} | {"id", "t":"error", "message"}   // exactly one, terminal

Concurrency equals the pool size: a worker handles one call at a time, and the daemon queues the rest until a worker frees up.

2.2: Per-call reset

Between calls a worker resets state (phlo('tech/reset'), session close, GC), exactly like the FrankenPHP worker loop, so one call never leaks into the next. Write worker-safe targets: no request or user state in statics, and commit or roll back DB work. Long-lived connections you explicitly mark with objPers survive; everything else is cleared.

2.3: Dynamic sizing, recycle

The pool sizes itself: it spawns a worker when a call arrives and none is free, up to a global cap of one less than the core count, and reaps a worker once it sits idle. Nothing is configured.

Whether an app runs one-shot (a fresh process per call, which keeps hot-reload working) or on the resident pool (warm workers, no per-call boot) is configured per-host in config/daemon.js (or per-call for CLI dispatch): a build: true dev app is one-shot, a release app is pooled. Two guards keep a resident pool healthy: a per-call timeout (a stuck worker is killed and respawned) and recycling (a worker is replaced after a number of calls, to shed any slow leak). After a deploy, restart the workers so they reload the new build.

2.4: Health

GET /health on the daemon reports the live worker total against the cap, each pool keyed by app path, the connected sockets per host, and the configured hosts:

{
    "status": "ok",
    "workers": 5,
    "cap": 7,
    "pools": { "/srv/app/release/www/app.php": { "workers": 4, "busy": 1, "queued": 0 } },
    "sockets": { "app.example.com": { "tokens": 12, "sockets": 18 } },
    "registered": ["app.example.com", "dev.example.com"]
}

busy is the workers currently running a call and queued the calls waiting for a free worker; workers/cap is the live total against the ceiling. A busy that sits at the cap with a growing queued means the node is at capacity: add nodes or give it more cores.

We use essential cookies to make this site work. With your permission we also use analytics to improve the site.