diff --git a/docs/source/development/cronjob.rst b/docs/source/development/cronjob.rst new file mode 100644 index 000000000..df72fa922 --- /dev/null +++ b/docs/source/development/cronjob.rst @@ -0,0 +1,93 @@ + +.. 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; + + class MyCronJob implements CronJobInterface + { + 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 = new DateTimeImmutable('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(): void + { + // here, we execute the command + } + } + +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. + + + diff --git a/docs/source/development/index.rst b/docs/source/development/index.rst index f35bc12db..52c541c8e 100644 --- a/docs/source/development/index.rst +++ b/docs/source/development/index.rst @@ -34,6 +34,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Useful snippets manual/index.rst Assets + Cron Jobs Layout and UI ************** diff --git a/docs/source/installation/prod.rst b/docs/source/installation/prod.rst index 4e670b8f8..f51141e8d 100644 --- a/docs/source/installation/prod.rst +++ b/docs/source/installation/prod.rst @@ -1,10 +1,12 @@ .. Copyright (C) 2014-2019 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". + 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". + +.. _prod: Installation for production ########################### @@ -36,12 +38,25 @@ This should be adapted to your needs: * Think about how you will backup your database. Some adminsys find easier to store database outside of docker, which might be easier to administrate or replicate. +Cron jobs +========= + +The command :code:`chill:cron-job:execute` should be executed every 15 minutes (more or less). + +This command should never be executed concurrently. It should be not have more than one process for a single instance. + +Post-install tasks +================== + +- import addresses. See :ref:`addresses`. + + Tweak symfony messenger ======================= Calendar sync is processed using symfony messenger. -You can tweak the configuration +You can tweak the configuration Going further: diff --git a/src/Bundle/ChillMainBundle/Cron/CronManager.php b/src/Bundle/ChillMainBundle/Cron/CronManager.php index 20934fbdb..52cba3d38 100644 --- a/src/Bundle/ChillMainBundle/Cron/CronManager.php +++ b/src/Bundle/ChillMainBundle/Cron/CronManager.php @@ -19,6 +19,25 @@ use Exception; use Psr\Log\LoggerInterface; use function array_key_exists; +/** + * Manage cronjob and execute them. + * + * 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 error inside job execution are catched (with the exception of _out of memory error_). + * + * The manager will mark the task as executed even if an error is catched. This will lead as failed job + * will not have priority any more on other tasks. + * + * If a tasks is "forced", there is no test about eligibility of the task (the `canRun` method is not called), + * and the last task execution is not recorded. + */ class CronManager implements CronManagerInterface { private const LOG_PREFIX = '[cron manager] '; diff --git a/src/Bundle/ChillMainBundle/Cron/CronManagerInterface.php b/src/Bundle/ChillMainBundle/Cron/CronManagerInterface.php index 3b9680918..57f96c74b 100644 --- a/src/Bundle/ChillMainBundle/Cron/CronManagerInterface.php +++ b/src/Bundle/ChillMainBundle/Cron/CronManagerInterface.php @@ -13,5 +13,11 @@ namespace Chill\MainBundle\Cron; interface CronManagerInterface { + /** + * Execute one job, with a given priority, or the given job (identified by his key) + * + * @param string|null $forceJob + * @return void + */ public function run(?string $forceJob = null): void; }