En el artículo anterior veíamos cómo funciona el sistema de tareas de Moodle desde una perspectiva arquitectónica: ejecución diferida, papel del cron y tipos de tareas.
En este segundo artículo bajamos al código. No como un recorrido sintáctico, sino como una forma de entender cómo esas decisiones de implementación terminan afectando al comportamiento del sistema en producción.
Una task no es solo una clase. Es un mecanismo que introduce trabajo diferido dentro de la plataforma, con impacto directo en datos, rendimiento y operación.
Dónde se definen las tareas
Una task tiene dos piezas bien diferenciadas dentro de un plugin:
db/tasks.phpclasses/task/my_task.php
Ejemplo de db/tasks.php para una scheduled task:
$tasks = [
[
'classname' => 'local_example\task\sync_users',
'minute' => '*/5',
'hour' => '*',
'day' => '*',
'month' => '*',
'dayofweek' => '*',
],
];
Este archivo forma parte de la configuración persistente del plugin, Moodle lo procesa durante instalación o upgrade.
Un matiz importante, modificar este archivo requiere subir la versión del plugin. Un purge de caché no es suficiente porque el sistema no reconstruye esta información dinámicamente.
Implementación de una scheduled task
Ejemplo básico:
namespace local_example\task;
class sync_users extends \core\task\scheduled_task {
public function get_name() {
return 'Sync users';
}
public function execute() {
mtrace('Starting user sync');
$manager = new \local_example\user_sync_manager();
$manager->sync();
mtrace('User sync finished');
}
}
La task se encarga de lanzar el proceso, pero no de implementar la lógica en sí. Esa lógica se mueve a una clase manager, que es donde realmente se define el comportamiento. De esta forma puedes usar ese mismo código desde otros puntos del sistema sin tener que repetirlo.
Implementación de una adhoc task
Ejemplo:
namespace local_example\task;
class process_user extends \core\task\adhoc_task {
public function execute() {
$data = $this->get_custom_data();
$manager = new \local_example\user_processor_manager();
$manager->process($data->userid);
}
}
La adhoc task no depende de una planificación fija, sino que se crea en el momento en que el sistema necesita delegar trabajo en segundo plano.
Esto hace que este tipo de tareas esté mucho más ligado al flujo de negocio. Cada inserción en la cola es consecuencia directa de una operación concreta, lo que a su vez puede hacer crecer la carga del sistema de forma menos predecible si no se controla.
Encolado:
$task = new \local_example\task\process_user();
$task->set_custom_data(['userid' => $userid]);
\core\task\manager::queue_adhoc_task($task);
Esto introduce trabajo en la cola en el momento en que ocurre una operación.
Procesamiento por lotes y control de carga
Ejemplo simplificado:
public function execute() {
global $DB;
$users = $DB->get_records('user', null, '', '*', 0, 100);
foreach ($users as $user) {
$this->process_user($user);
}
mtrace('Batch processed');
}
Aquí empiezan las decisiones que afectan al sistema:
- cuántos registros procesas en cada ejecución,
- cuánto tiempo mantiene la task el proceso ocupado,
- qué carga introduces sobre base de datos.
Aunque el código resulta bastante directo, las implicaciones en tiempo de ejecución dependen del volumen de datos, la frecuencia de ejecución y la carga que se introduce sobre la base de datos.
Manejo de errores y reintentos
Ejemplo:
public function execute() {
try {
$this->run_sync();
} catch (\Throwable $e) {
mtrace('Error: ' . $e->getMessage());
throw $e;
}
}
Lanzar la excepción permite que Moodle gestione el reintento.
Static caches dentro del runner
El runner de tareas puede ejecutar varias tasks dentro del mismo proceso PHP.
Algunas APIs del core mantienen datos en memoria mediante static cache.
Cuando una task modifica datos y otra se ejecuta a continuación, puede ocurrir que el estado en memoria no refleje los cambios recientes y se trabajen datos desactualizados.
Para estos casos, Moodle permite limpiar las static caches con el método: \core\task\manager::clear_static_caches()
No siempre es necesario, pero en tareas que modifican estado de forma intensiva ayuda a evitar inconsistencias entre ejecuciones consecutivas. Esto fuerza a que las siguientes tareas trabajen con un estado limpio.
Idempotencia y reentrancia
Ejemplo simplificado:
public function process_user($user) {
if ($this->already_processed($user->id)) {
return;
}
// Process...
$this->mark_as_processed($user->id);
}
Este patrón permite reintentos seguros, evitar duplicados y mantener la coherencia de datos incluso en casos de fallos parciales o reinicios inesperados.
Logging con contexto útil
Ejemplo:
mtrace('Processing user ' . $user->id);
Con esto tendríamos suficiente para localizar errores, entender progreso y acotar incidencias. Sin embargo, en entornos con volumen real, es más que recomendable incluir más contexto.
Ejecución manual de tareas desde CLI
Además de ejecutarse automáticamente mediante el cron, las tareas pueden lanzarse manualmente, lo que resulta especialmente útil en desarrollo, debugging o en situaciones donde se necesita forzar una ejecución sin esperar a la planificación habitual.
En el caso de las scheduled tasks, Moodle permite ejecutarlas directamente desde CLI:
php admin/cli/scheduled_task.php --execute=\\local_example\\task\\sync_users
Esto lanza únicamente esa tarea, respetando su implementación pero sin depender del cron.
Para las adhoc tasks, el script permite procesar la cola completa o filtrar por clase:
php admin/cli/adhoc_task.php -e
El parámetro -e (--execute) indica que se deben ejecutar las tareas. Si no se incluye, el script muestra información sin procesarlas. Si le pasas el parámetro --classname, --keepalive o --taskslimit, asume el --execute.
Para ejecutar únicamente las tareas de una clase concreta:
php admin/cli/adhoc_task.php --classname=\\local_example\\task\\process_user
Como cualquier otro script CLI, estos comandos deben ejecutarse con el mismo usuario que ejecuta PHP en el entorno web, para evitar problemas de permisos sobre ficheros, cachés o directorios de datos. En sistemas tipo Debian, lo habitual es hacerlo así:
sudo -u www-data php admin/cli/scheduled_task.php --execute=\\local_example\\task\\sync_users
Para los que uséis moodle-docker, el comando sería algo como esto:
bin/moodle-docker-compose exec php php admin/cli/scheduled_task.php --execute=\\local_example\\task\\sync_users
También es posible ejecutar tareas desde la interfaz de administración de Moodle, donde se pueden lanzar manualmente las scheduled tasks o revisar el estado de las adhoc.
En la práctica, la ejecución desde consola suele ser más rápida y directa, especialmente cuando se está desarrollando o investigando un problema, ya que evita la sobrecarga de la interfaz, reduce la dependencia del ratón y permite un control más preciso sobre la ejecución.
Como referencia general, para cualquier script CLI (y cualquiera que programes) conviene revisar las opciones disponibles mediante:
php admin/cli/scheduled_task.php --help
php admin/cli/adhoc_task.php --help
De código a comportamiento del sistema
Implementar una task es relativamente directo.
Lo relevante es entender que cada decisión introduce comportamiento en el sistema:
- cómo se distribuye la carga,
- cómo se recupera ante errores,
- cómo se mantiene la coherencia de datos,
- cómo se diagnostican incidencias.
En entornos con volumen real, estas tareas dejan de ser código aislado y pasan a formar parte del funcionamiento operativo de la plataforma.
En el siguiente y último artículo de esta serie entraremos en ese nivel, analizando qué ocurre cuando el cron no acompaña, cuando las colas crecen o cuando el sistema empieza a comportarse de forma impredecible bajo carga.