Moodle Background Processing: Scheduled vs. Ad-hoc Tasks
Choosing between scheduled and ad-hoc tasks in Moodle 4.x is critical for site performance. Learn when to use cron-based periodic tasks versus queue-based event tasks to avoid blocking your system.
10 Oct 2025, 05:13 UTC

The Background Processing Dilemma
When building a Moodle plugin, you often encounter a process that is too heavy for a standard HTTP request—such as generating a massive PDF report, syncing 5,000 users from an external API, or sending a batch of notifications. If you run these in the browser, the user hits a timeout; if you run them synchronously, the site freezes.
Moodle 4.x provides two primary mechanisms for offloading this work: Scheduled Tasks and Ad-hoc Tasks. Choosing the wrong one can lead to a "clogged" cron queue where critical system maintenance is blocked by a single hanging report, or a database bloated with thousands of orphaned task records.
Scheduled Tasks: The Heartbeat
Scheduled tasks are periodic. They are defined in your plugin's db/tasks.php file and are triggered by the system cron. Think of these as the "heartbeat" of your plugin—they handle work that must happen every hour, day, or week regardless of specific user actions.
- Execution: They run sequentially within the cron process. If Task A hangs, Task B (and potentially the rest of Moodle's system cron) waits.
- State: They are stateless and parameterless. You cannot tell a scheduled task to "process only Course ID 42"; it must be written to find what needs processing itself.
- Best Use Case: Daily cleanup of expired temporary files or weekly summary emails.
Ad-hoc Tasks: The On-Demand Queue
Ad-hoc tasks are event-driven. They are queued via the \core\task\manager and processed by a worker. Unlike scheduled tasks, these are designed for unpredictable, high-volume work triggered by a specific event.
- Execution: They are processed by the task runner (
php admin/cli/task.php). Because they can be run by multiple concurrent CLI workers, they offer true parallelism. - State: They accept constructor arguments. You can pass a specific User ID or File ID into the task, which Moodle serializes into the
mdl_task_adhoctable. - Resilience: They feature built-in exponential backoff. If an ad-hoc task fails due to a temporary API timeout, Moodle will automatically retry it later.
- Best Use Case: Sending a confirmation email after a user clicks "Submit" or processing a specific CSV upload.
Decision Matrix: Which one to use?
| Feature | Scheduled Task | Ad-hoc Task |
|---|---|---|
| Trigger | Time-based (Cron) | Event-based (Code) |
| Parameters | None (Stateless) | Supported (Serialized) |
| Concurrency | Sequential | Parallel (with multiple workers) |
| Retry Logic | Manual/Custom | Automatic Exponential Backoff |
Worked Example: The "Hybrid Pattern"
A common engineering mistake is using a single scheduled task to process a massive queue of items. If the task hits a memory limit halfway through, it may restart from the beginning or leave the system in an inconsistent state. The professional approach is the Hybrid Pattern: use a scheduled task to discover work, and ad-hoc tasks to execute it.
Scenario: You need to regenerate certificates for all students who completed a course today.
Step 1: The Scheduled Task (The Discoverer)
In db/tasks.php, define a task that runs every hour. Its execute() method does not generate PDFs; it simply queries the database for eligible students and queues an ad-hoc task for each one.
// Inside Scheduled Task execute() method
$students = $this->get_students_needing_certificates();
foreach ($students as $student) {
// Queue a separate ad-hoc task for each student
\core\task\manager::queue_adhoc_task(new \local_cert_plugin\task\generate_cert_task($student->id));
}
Step 2: The Ad-hoc Task (The Worker)
Create a class extending \core\task\adhoc_task. This class takes the student ID in the constructor and performs the heavy lifting.
public function execute() {
$studentid = $this->studentid;
// Perform heavy PDF generation here
// If this fails, Moodle will retry only this specific student's certificate
}
Critical Limitations and Risks
Serialization Pitfalls: When queuing ad-hoc tasks, never pass complex objects (like a database connection or a full User object) into the constructor. Moodle serializes these to the database. If the object contains a resource or a closure, the task will fail upon retrieval. Always pass IDs (integers/strings) and re-fetch the object inside the execute() method.
Worker Bottlenecks: Ad-hoc tasks are only as fast as your worker configuration. If you only run php admin/cli/task.php once via a system cron, you have no parallelism. To truly scale, use a process manager like supervisord or systemd to keep multiple instances of the task runner active.
Verification and Monitoring
To verify your tasks are working as expected:
- Manual Trigger: Run
php admin/cli/task.php --oncefrom the terminal to process a single ad-hoc task and observe any PHP errors in real-time. - Log Inspection: Navigate to Site Administration > Server > Tasks > Task logs to see the success/failure history of scheduled tasks.
- Database Check: Run
SELECT count(*) FROM mdl_task_adhoc;. If this number grows indefinitely, your workers are not keeping up with the queue, or tasks are failing and retrying indefinitely.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.