docs: cron and background work on Drupal, and the two missing limits options - #30
docs: cron and background work on Drupal, and the two missing limits options#30andypost wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4b07653c72
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| **pcntl_alarm()** bounds a job. **set_time_limit()** does not: it does not | ||
| interrupt a blocking sleep or a blocking I/O call. |
There was a problem hiding this comment.
Document the setup needed for pcntl_alarm
When a timeout handler is used to release Drupal's lock as recommended below, pcntl_alarm() only schedules SIGALRM; it does not by itself ensure that the handler runs during blocking I/O. PCNTL must be installed, a handler must be registered with asynchronous dispatch (or signals explicitly dispatched), and restarting syscalls must be handled appropriately. Without that setup, readers can replace set_time_limit() as advised and still leave the runaway job unbounded, so provide a complete safe recipe or qualify this claim.
Useful? React with 👍 / 👎.
…options Drupal 11.4 bootstraps through symfony/runtime, whose HttpKernelRunner calls fastcgi_finish_request() before $kernel->terminate(). Unit's PHP module implements that call, so the router counts the process idle while PHP is still running the terminate phase: processes.max overstates capacity, and the idle reaper can deregister the process mid-job. A request queued behind it hangs, and limits.timeout does not cover it. Document that, recommend running cron with drush from a systemd timer instead of automated_cron, and describe what does and does not bound a job. Also correct the limits object: it has four options, not two. Document start_timeout and shm, and note that "spare": 1 is what keeps a process warm, not "idle_timeout": 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4b07653 to
edc43da
Compare
|
All five applied, now 1. You were right that the page asserted a mechanism it did not show, and checking it turned out to make the claim more precise rather than force a hedge. The timer is armed on the path that hands a request to a process — 2. The paragraph now links freeunitorg/freeunit#321 inline, so a reader of the page reaches the tracker without going through this PR. 3. The stranding sentence is hedged at the point of claim — "can be left with no process to serve it", "observed rather than derived", with the 300-second figure marked as one measurement and #321 named as holding the conditions it ran under. The mitigation hedge below is now about mitigations only, which is what it was for. 4. Summary order matches the table: 5. Sphinx build is clean on both touched files. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: edc43da716
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| |app| 10 and 11.0-11.3 have no **symfony/runtime**: **index.php** calls | ||
| **$response->send()** and then **$kernel->terminate()** with the connection | ||
| still open, so the visitor waits for the terminate work. That is a latency | ||
| problem, not a capacity one, and the rest of this section does not apply to | ||
| those versions. |
There was a problem hiding this comment.
Cover older Drupal releases that also detach cron
On Drupal 10 and 11.0–11.3, Symfony HttpFoundation's default Response::send() itself calls fastcgi_finish_request() when that function is available, and Unit provides it. Consequently, $kernel->terminate() already runs after the response is detached on these releases, so the same capacity, idle-reaping, and queued-request risks described below apply. Excluding these versions tells affected operators that they do not need the external-cron mitigation.
Useful? React with 👍 / 👎.
Two things: a new section on the Drupal page about running cron and background work, and a correction to the
limitsreference.Cron and background work
howto/drupal.rststopped at the request path. It said nothing about cron, queues, or long-running jobs — which is where Drupal on an application server behaves differently from Drupal behind php-fpm, and where the defaults bite.The section covers, for Drupal 11.4 or later (from 11.4 Drupal bootstraps through
symfony/runtime, whoseHttpKernelRunnercallsfastcgi_finish_request()before$kernel->terminate(); Drupal 10 and 11.0-11.3 have nosymfony/runtimeand the visitor waits for cron instead):fastcgi_finish_request()means on Unit. The router treats the request as finished while PHP is still executing the terminate phase, soprocesses.maxoverstates real capacity, and the worker's idle clock starts. With the defaultidle_timeoutof 15s the reaper can deregister that worker mid-job; the job finishes, but a request already queued to it hangs — it is not 503'd, because the router no longer has a request to time out.automated_cronis the default trigger and runs for up to 240s (Cron.phpsets the limit, the module invokes it). Onprocesses.max: 1that is an outage for the duration.automated_cronor set its interval to 0, and run cron externally —drush cronfrom a systemd timer. Queues likewise, viadrush queue:run.processes.spare >= 1and anidle_timeoutlonger than the worst-case job, both stated as mitigations rather than guarantees, with the spurious[alert] sendmsg(...)cutover lines thatspare >= 1produces.pcntl_alarm()works;set_time_limit()does not interrupt a blocking sleep or I/O, and a timeout handler must release Drupal's cron lock explicitly or cron is skipped for up to 900s.PUTandSIGTERM, because a process that has announced itself as ready is never escalated to a signal — so a job finishes by default, with no timeout and no upper bound.docker stop's 10s grace will kill it.APP_DEBUGwarning: Symfony's runner skipsfastcgi_finish_request()in debug, so the detach silently disappears.No
pcntl_forkrecipe and no performance claim: the fork approach measures 0.97x against an already-warm worker, and the detach it is reaching for already exists.limitsThe reference said
limits"accepts two integer options". There are four.start_timeout(seconds Unit waits for a new application process to become ready, default 0 = unbounded) andshm(per-process shared-memory budget for request and response transfer, default 100 MiB) were undocumented. The "Unit doesn't detect freezes" note is reworded so it no longer reads as "there is no start timeout".processesgains one sentence:spare: 1keeps a warm process regardless ofidle_timeout, and"idle_timeout": 0alone does not.Behaviour described here was verified against the router and the PHP module; the capacity and stranding behaviour is tracked as freeunitorg/freeunit#321.
🤖 Generated with Claude Code
https://claude.ai/code/session_0143ggPMsUKpRPWoQLTxBZUk