The "Scheduled Tasks" system allows to define list of tasks, where each task:
- has own execution scheduled defined in CRON expression
- does what's it's supposed to do
The pending scheduled tasks are executed from "/tools/cron.php" process, that runs each minute (via CRON). Current implementation of "\kScheduledTaskManager::runAll" method (runs scheduled tasks) is preselecting all enabled or timed out scheduled tasks upfront and then iterates on them.
In case of one of the tasks executed long enough for another "/tools/cron.php" process to be started we'll end up in racing condition where 1st process (that has long running scheduled task) would see all scheduled tasks after it as "available for execution", where in reality they already might have executed by 2nd process and so on (there can be many parallel "cron.php" processes). This results in a side effect, where scheduled task is executed more times, then it's supposed to be executed according to it's schedule.
Solution
In the "\kScheduledTaskManager::runAll" method, when iterating over "$events_source" array and after site domain limitation was checked:
- query "LastRunOn" column value of current scheduled task from database
- if it doesn't match "$event_data['LastRunOn']" (means parallel process already executed it and update the date), then continue to next scheduled task
Quote: 1h