You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add an experimental `worker_timeout` worker option: a hard per-request
timeout for worker mode, the equivalent of PHP-FPM's
request_terminate_timeout. When a worker request runs longer than the
timeout it is aborted with a "Worker request timeout of N second(s)
exceeded" fatal and the worker restarts cleanly for the next request.
Unlike max_execution_time, this also covers time spent blocked in an
external call. A signal/EINTR alone cannot abort such a call (PHP retries
EINTR, and mysqlnd even drops its socket from EG(regular_list)), so on
Linux the watchdog inspects what the thread is parked in via
/proc/self/task/<tid>/syscall and shuts down the socket(s) involved:
- read/recvfrom/recvmsg/connect: fd is the syscall's first argument;
- poll/ppoll: the pollfd array is read from the process's own memory with
process_vm_readv(2) (PHP's stream layer, and Redis/HTTP/DB clients on
it, always poll before reading). Both syscalls are matched: glibc and
musl implement poll() via the dedicated poll syscall on arches that
have one (e.g. amd64) and via ppoll only elsewhere (e.g. arm64);
- epoll_wait/epoll_pwait: watched fds are enumerated from
/proc/self/fdinfo/<epfd> (covers curl_multi, gRPC).
Every fd is confirmed to be a socket, and after recovering a
pointer/table-derived fd the thread's syscall is re-read to confirm it is
still parked there before shutdown, so a stale pointer or reused fd cannot
close an unrelated descriptor. The watchdog body runs under the same mutex
as its cancellation, so a watchdog racing request completion can never
interrupt the wrong request. A long sleep() is woken by the realtime
kill signal (Linux/FreeBSD). The fatal is raised at the next opcode via a
custom zend_interrupt_function (guarded against double installation across
embedded Init/Shutdown cycles). On macOS/Windows only the VM-interrupt
flag is set (CPU-bound overruns are caught; a blocking syscall already in
progress cannot be unblocked).
Configurable per worker via the Caddyfile `worker_timeout` directive and
the WithWorkerTimeout API; defaults to 0 (disabled).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
// WorkerTimeout sets a hard per-request timeout (e.g. 30s). A worker request running longer is interrupted so the thread can be reclaimed. 0 (default) disables it.
Copy file name to clipboardExpand all lines: docs/config.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -111,6 +111,7 @@ You can also explicitly configure FrankenPHP using the [global option](https://c
111
111
watch <path> # Sets the path to watch for file changes. Can be specified more than once for multiple paths.
112
112
name <name> # Sets the name of the worker, used in logs and metrics. Default: absolute path of worker file
113
113
max_consecutive_failures <num> # Sets the maximum number of consecutive failures before the worker is considered unhealthy, -1 means the worker will always restart. Default: 6.
114
+
worker_timeout <duration> # (experimental) Hard per-request timeout (e.g. 30s). A request running longer is interrupted so the worker thread can be reclaimed. Default: 0 (disabled).
0 commit comments