Skip to content

docs: cron and background work on Drupal, and the two missing limits options - #30

Open
andypost wants to merge 1 commit into
freeunitorg:mainfrom
andypost:docs/drupal-cron-and-background
Open

docs: cron and background work on Drupal, and the two missing limits options#30
andypost wants to merge 1 commit into
freeunitorg:mainfrom
andypost:docs/drupal-cron-and-background

Conversation

@andypost

Copy link
Copy Markdown

Two things: a new section on the Drupal page about running cron and background work, and a correction to the limits reference.

Cron and background work

howto/drupal.rst stopped 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, whose HttpKernelRunner calls fastcgi_finish_request() before $kernel->terminate(); Drupal 10 and 11.0-11.3 have no symfony/runtime and the visitor waits for cron instead):

  • What fastcgi_finish_request() means on Unit. The router treats the request as finished while PHP is still executing the terminate phase, so processes.max overstates real capacity, and the worker's idle clock starts. With the default idle_timeout of 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_cron is the default trigger and runs for up to 240s (Cron.php sets the limit, the module invokes it). On processes.max: 1 that is an outage for the duration.
  • The recommendation: uninstall automated_cron or set its interval to 0, and run cron externally — drush cron from a systemd timer. Queues likewise, via drush queue:run.
  • If cron must stay in-request: processes.spare >= 1 and an idle_timeout longer than the worst-case job, both stated as mitigations rather than guarantees, with the spurious [alert] sendmsg(...) cutover lines that spare >= 1 produces.
  • Bounding a job: 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.
  • Deploys: an in-flight job survives idle reaping, a config PUT and SIGTERM, 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.
  • An APP_DEBUG warning: Symfony's runner skips fastcgi_finish_request() in debug, so the detach silently disappears.

No pcntl_fork recipe and no performance claim: the fork approach measures 0.97x against an already-warm worker, and the detach it is reaching for already exists.

limits

The 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) and shm (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".

processes gains one sentence: spare: 1 keeps a warm process regardless of idle_timeout, and "idle_timeout": 0 alone 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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread source/howto/drupal.rst
Comment on lines +298 to +299
**pcntl_alarm()** bounds a job. **set_time_limit()** does not: it does not
interrupt a blocking sleep or a blocking I/O call.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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>
@andypost
andypost force-pushed the docs/drupal-cron-and-background branch from 4b07653 to edc43da Compare September 13, 2026 01:54
@andypost

Copy link
Copy Markdown
Author

All five applied, now edc43da.

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 — nxt_router.c:5297 and :5669, both if (app->timeout != 0) — while a request that cannot be dispatched is parked in app->ack_waiting_req (:6890) with no timer at all. So "no deadline running" follows from where the arming sites are. The page now says that, adds that the option defaults to 0 (which arms nothing regardless), and reports the absent 503 as what the measurement saw rather than as a rule.

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: requests, shm, start_timeout, timeout.

5. shm reads "per-application-process limit".

Sphinx build is clean on both touched files.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread source/howto/drupal.rst
Comment on lines +221 to +225
|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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

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.

1 participant