From 7afa6591c09f784ffee8c257223bc4a4e7be664b Mon Sep 17 00:00:00 2001 From: EpopE Date: Thu, 30 Apr 2026 12:02:10 +0200 Subject: [PATCH] Implement catch-up behavior for cron jobs Add a cron_catchup system option to control how missed executions are handled. When disabled, cronjobs are always rescheduled from the current time, avoiding catch-up loops after downtime. When enabled, the existing behavior is preserved, with a safeguard against outdated nextrun values to prevent infinite execution loops. --- .../processors/web/cron/run.class.php | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/core/components/cronmanager/processors/web/cron/run.class.php b/core/components/cronmanager/processors/web/cron/run.class.php index f0ecc5e..2bbee81 100755 --- a/core/components/cronmanager/processors/web/cron/run.class.php +++ b/core/components/cronmanager/processors/web/cron/run.class.php @@ -69,10 +69,30 @@ private function runCronjobs($success) } $c->sortby('nextrun'); + // Get catch-up behavior option (default: enabled for backward compatibility) + // 1 = enable catch-up (original behavior) + // 0 = disable catch-up (reschedule from current time) + $catchup = (int)$this->cronmanager->getOption('cron_enable_catchup', null, 0); + /** @var modCronjob $cronjob */ $cronjobs = $this->modx->getIterator('modCronjob', $c); foreach ($cronjobs as $cronjob) { + // Determine initial base datetime for scheduling $rundatetime = ($cronjob->get('nextrun')) ? $cronjob->get('nextrun') : date('Y-m-d H:i:s'); + + if (!$catchup) { + // Catch-up disabled: + // Always reschedule based on the current time to avoid executing missed runs + $rundatetime = date('Y-m-d H:i:s'); + } else { + // Catch-up enabled: + // If nextrun is significantly in the past, reset to current time + // This prevents excessive back-to-back executions ("catch-up loops") + if (strtotime($rundatetime) < time() - 60) { + $rundatetime = date('Y-m-d H:i:s'); + } + } + $properties = $cronjob->get('properties'); if (!empty($properties)) { /** @var modPropertySet $propset */ @@ -140,9 +160,9 @@ private function runCronjobs($success) $logs = [$log]; $cronjob->set('lastrun', $rundatetime); - if ($this->cronmanager->getOption('daylight_saving') && $cronjob->get('minutes') > 60) { + if ($this->cronmanager->getOption('daylight_saving') && $cronjob->get('minutes') > 60) { $cronjob->set('nextrun', date('Y-m-d H:i:s', strtotime($cronjob->get('minutes') . ' minutes', (strtotime($rundatetime))))); - } else { + } else { $cronjob->set('nextrun', date('Y-m-d H:i:s', (strtotime($rundatetime) + ($cronjob->get('minutes') * 60)))); } $cronjob->set('running', false);