Moodle cron memory growth: is scheduled‑task cleanup leaking?
0 reputation · 27 Oct 2020, 14:59 UTC
0 reputation · 27 Oct 2020, 14:59 UTC
When running php admin/cli/cron.php on a site, the process RSS steadily rises, even though each scheduled task completes quickly. The feature under scrutiny is Moodle’s cron‑driven task processing, which handles both scheduled tasks (executed on a timetable) and adhoc tasks (queued for asynchronous work). The main uncertainty lies in whether the growth is caused by per‑task cleanup, the worker’s process lifetime, or the retry/backoff policy that keeps failed adhoc tasks in memory.
Because PHP’s memory_get_usage reports allocation and the OS RSS reflects allocator reuse, a single successful run cannot distinguish between a true leak and delayed garbage collection. The goal is to attribute the memory increase to a specific part of the task lifecycle and decide on a corrective strategy that preserves throughput while preventing runaway memory use.
Which component of the task processing chain is most likely responsible for the sustained RSS rise? Does the current retry/backoff implementation retain task objects across cron invocations? Would recycling the cron/worker process after each scheduled task mitigate the growth without sacrificing performance?
35275 reputation · 27 Oct 2020, 23:06 UTC
The most likely cause is not a leak in task cleanup and not the retry/backoff logic — it is the lifetime of the single PHP process that runs admin/cli/cron.php. Moodle executes all due scheduled tasks and then drains the adhoc queue inside one process. PHP's Zend allocator frees memory for reuse inside the process but rarely returns it to the OS, so RSS climbs monotonically even when every task cleans up correctly. Globals like $DB, $CFG, and the MUC caches also accumulate a retained baseline as more code and definitions load.
No. Failed adhoc tasks are written back to the mdl_task_adhoc table with an incremented faildelay (exponential backoff) and re-read from the database on the next run. Retry state lives in the database, not in process memory, so a growing backlog adds DB rows and work per run — which can indirectly raise memory by making each run longer — but it does not pin PHP objects across invocations. You can confirm this directly:
SELECT classname, COUNT(*) AS n, MAX(faildelay) AS max_delay
FROM mdl_task_adhoc GROUP BY classname ORDER BY n DESC;
RSS alone cannot distinguish the two. Instrument a run by logging memory_get_usage(true) (real emalloc allocation) before and after each task, alongside RSS from getrusage() or /proc/self/status:
memory_get_usage(true) returns to baseline after each task while RSS keeps climbing, you are seeing allocator retention — expected, and not a bug in your tasks.Also compare with Opcache disabled for CLI (php -n admin/cli/cron.php or opcache.enable_cli=0) to rule out shared-segment artifacts inflating RSS readings.
Restarting PHP per task would reset RSS, but it fights Moodle's design: each spawn re-pays bootstrap, config load, and cache warmup, so throughput drops noticeably on sites with many small tasks. Cheaper mitigations, in order of preference:
MemoryMax= or a wrapper that exits after N minutes — so the OS recycles the process between runs rather than mid-task.Avoid hard-killing cron mid-task: it can leave stale cron locks and requeue adhoc tasks. Verify exact runner behavior (lock handling, faildelay backoff) against your installed release's lib/classes/task, since internals vary between Moodle versions.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.