
.. Copyright (C)  2014-2023 Champs Libres Cooperative SCRLFS
    Permission is granted to copy, distribute and/or modify this document
    under the terms of the GNU Free Documentation License, Version 1.3
    or any later version published by the Free Software Foundation;
    with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
    A copy of the license is included in the section entitled "GNU
    Free Documentation License".

.. _cronjob:

Cron jobs
*********

Some tasks must be executed regularly: refresh some materialized views, remove old data, ...

For this purpose, one can programmatically implements a "cron job", which will be scheduled by a specific command.

The command :code:`chill:cron-job:execute`
==========================================

The command :code:`chill:cron-job:execute` will schedule a task, one by one. In a classical implementation, it should
be executed every 15 minutes (more or less), to ensure that every task can be executed.

.. warning::

   This command should not be executed in parallel. The installer should ensure that two job are executed concurrently.

How to implements a cron job ?
==============================

Implements a :code:`Chill\MainBundle\Cron\CronJobInterface`. Here is an example:

.. code-block:: php

    namespace Chill\MainBundle\Service\Something;

    use Chill\MainBundle\Cron\CronJobInterface;
    use Chill\MainBundle\Entity\CronJobExecution;
    use DateInterval;
    use DateTimeImmutable;
    use Symfony\Component\Clock\ClockInterface;

    class MyCronJob implements CronJobInterface
    {
        function __construct(private ClockInterface $clock) {}

        public function canRun(?CronJobExecution $cronJobExecution): bool
        {
            // the parameter $cronJobExecution contains data about the last execution of the cronjob
            // if it is null, it should be executed immediatly
            if (null === $cronJobExecution) {
                return true;
            }

            if ($cronJobExecution->getKey() !== $this->getKey()) {
                throw new UnexpectedValueException();
            }

            // this cron job should be executed if the last execution is greater than one day, but only during the night

            $now = $clock->now();

            return $cronJobExecution->getLastStart() < $now->sub(new DateInterval('P1D'))
                && in_array($now->format('H'), self::ACCEPTED_HOURS, true)
                // introduce a random component to ensure a roll of task execution when multiple instances are hosted on same machines
                && mt_rand(0, 5) === 0;
        }

        public function getKey(): string
        {
            return 'arbitrary-and-unique-key';
        }

        public function run(array $lastExecutionData): void
        {
            // here, we execute the command

            // we return execution data, which will be served for next execution
            // this data should be easily serializable in a json column: it should contains
            // only int, string, etc. Avoid storing object
            return ['last-execution-id' => 0];
       }
    }

How are cron job scheduled ?
============================

If the command :code:`chill:cron-job:execute` is run with one or more :code:`job` argument, those jobs are run, **without checking that the job can run** (the method :code:`canRun` is not executed).

If any :code:`job` argument is given, the :code:`CronManager` schedule job with those steps:

* the tasks are ordered, with:
   * a priority is given for tasks that weren't never executed;
   * then, the tasks are ordered, the last executed are the first in the list
* then, for each tasks, and in the given order, the first task where :code:`canRun` return :code:`TRUE` will be executed.

The command :code:`chill:cron-job:execute` execute **only one** task.



