#2657·frankenphp

Follow-ups for the background worker stack

Author: nicolas-grekasCreated Sep 18, 2026Updated Sep 18, 2026

Things left to do or decide after #2617 and its stack, so they don't get lost.

  • Validate a configuration before stopping the running one: Start() calls Init() after the old app stopped, so any declaration error leaves the site down while Caddy rolls the configuration back. A side-effect-free preflight shared with Init(), called from Validate(). https://github.com/php/frankenphp/pull/2617#discussion_r3987787721

  • Synchronize the startup-failure boundary: publishing ShuttingDown wakes the startup waiter before the thread finished its exit path, and a shutdown request is not distinguished from a thread that exited. Both worker handlers share that path. https://github.com/php/frankenphp/pull/2617#discussion_r3987787784

  • Pull the rest of the common bookkeeping into workerLifecycle once that boundary has one shape. https://github.com/php/frankenphp/pull/2617#discussion_r3987787771

  • num_regular_threads / max_regular_threads, so nobody computes the thread budget by hand. https://github.com/php/frankenphp/pull/2617#discussion_r3866053582

  • Implement Io\Poll\Handle on FrankenPHP\WorkerPollHandle once PHP 8.6 can be tested in CI, so the handle registers on an Io\Poll\Context directly instead of going through StreamPollHandle. The interface is an empty marker, the contract is the php_poll_handle_ops hooks. https://github.com/php/frankenphp/pull/2617#issuecomment-5731032156

  • Drop the per-task php_stream from the common path of #2636, now that a handle's lifetime is exactly the task's: hold the task state in the object, settle in free_obj, and build the stream lazily in getStream(), so a script that never multiplexes allocates no stream and no resource per task. Then recycle the handles themselves, through a per-thread free list. Channels stay eventfds on Linux; on macOS look for the equivalent, a kqueue descriptor triggered with EVFILT_USER is pollable and needs no pair, a pipe is the usual fallback (libuv's async handles), and Windows keeps the socket pair for php_select().