Skip to content

feat(worker): add per-worker request_idle_timeout#2467

Open
GrzegorzDrozd wants to merge 1 commit into
php:mainfrom
GrzegorzDrozd:add-iddle-timeout
Open

feat(worker): add per-worker request_idle_timeout#2467
GrzegorzDrozd wants to merge 1 commit into
php:mainfrom
GrzegorzDrozd:add-iddle-timeout

Conversation

@GrzegorzDrozd
Copy link
Copy Markdown

Workers block in frankenphp_handle_request() with no way to wake up during quiet periods, so long-lived connections (database, Redis) can go stale, and warm in-memory state has no chance to refresh until the next request arrives.

This adds an opt-in, per-worker idle timeout. When it's configured, after that long without a request frankenphp_handle_request() returns the new FRANKENPHP_REQUEST_IDLE_TIMEOUT constant (-1) instead of blocking. No request is handled, so the worker can reconnect or refresh settings and feature flags and then keep looping, without restarting the script and losing its warm state.

The return type widens from bool to int|bool. -1 is only ever emitted when the timeout is set, so existing scripts that check true/false are unaffected, and the feature is off by default.

  • Caddyfile: request_idle_timeout inside a worker block; 0 (the default) disables it, negative values are rejected
  • on an idle tick the request superglobals are emptied ($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER, $_REQUEST) and $_SESSION is dropped, while $_ENV and $GLOBALS are left untouched
  • idle ticks don't count toward max_requests
  • adds the FRANKENPHP_REQUEST_IDLE_TIMEOUT constant, docs, and tests

Workers block in frankenphp_handle_request() with no way to wake up
during quiet periods, so long-lived connections (database, Redis) can
go stale, and warm in-memory state has no chance to refresh until the
next request arrives.

This adds an opt-in, per-worker idle timeout. When it's configured,
after that long without a request frankenphp_handle_request() returns
the new FRANKENPHP_REQUEST_IDLE_TIMEOUT constant (-1) instead of
blocking. No request is handled, so the worker can reconnect or refresh
settings and feature flags and then keep looping, without restarting
the script and losing its warm state.

The return type widens from bool to int|bool. -1 is only ever emitted
when the timeout is set, so existing scripts that check true/false are
unaffected, and the feature is off by default.

- Caddyfile: request_idle_timeout <duration> inside a worker block;
0 (the default) disables it, negative values are rejected
- on an idle tick the request superglobals are emptied ($_GET, $_POST,
$_COOKIE, $_FILES, $_SERVER, $_REQUEST) and $_SESSION is dropped,
while $_ENV and $GLOBALS are left untouched
- idle ticks don't count toward max_requests
- adds the FRANKENPHP_REQUEST_IDLE_TIMEOUT constant, docs, and tests
@GrzegorzDrozd
Copy link
Copy Markdown
Author

Project that I am working on uses heavy edge caching, and it is timezone sensitive. It can be minutes between backend post requests and I saw in the logs issues with DB connection going stale. I thought having this idle timeout could solve this issue.

@AlliBalliBaba
Copy link
Copy Markdown
Contributor

AlliBalliBaba commented Jun 7, 2026

Is there ever a scenario where you might want to have a hard limit on how long a worker can run instead of how long it can wait for a request? Then you could just restart the worker with a background goroutine (for example by just calling thread.reboot() after x seconds)

@GrzegorzDrozd
Copy link
Copy Markdown
Author

Is there ever a scenario where you might want to have a hard limit on how long a worker can run instead of how long it can wait for a request? Then you could just restart the worker with a background goroutine (for example by just calling thread.reboot() after x seconds)

A background goroutine calling thread.reboot() after X seconds races with the request lifecycle. reboot() only succeeds when the thread is in Ready state (the CAS returns false otherwise), so if it fires mid-request it silently no-ops as far as I understand.
The idle timeout deliberately avoids restarting so the worker keeps its warm state and just refreshes connections or caches. A time-based "max lifetime" is the opposite, it's really the time-based sibling of max_requests.

@AlliBalliBaba
Copy link
Copy Markdown
Contributor

AlliBalliBaba commented Jun 7, 2026

Oh, I get it now, you are basically pinging frankenphp_handle_request() if it's idle for too long. I still I think restarting the worker would be much easier since you don't have to reset anything manually on application side (reboot wouldn't cause a race or noop).
But if you insist on the pinging approach I think a more general solution could look something like this:

worker /path/to/worker.php {
   ping idle 10s "/refresh" # send a request on path /refresh or directly return "/refresh" from frankenphp_handle_request()
}

# or 
worker /path/to/worker.php {
   ping 10s "/refresh" # there are probably many other use cases for just pinging repeatedly
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants