diff --git a/src/Symfony/Component/Scheduler/CHANGELOG.md b/src/Symfony/Component/Scheduler/CHANGELOG.md index e166a5d2f6b53..2fb6b75be694d 100644 --- a/src/Symfony/Component/Scheduler/CHANGELOG.md +++ b/src/Symfony/Component/Scheduler/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.2 +--- + + * Add capability to skip missed periodic tasks, only the last schedule will be called + 6.4 --- diff --git a/src/Symfony/Component/Scheduler/Generator/MessageGenerator.php b/src/Symfony/Component/Scheduler/Generator/MessageGenerator.php index 2c02f29344b89..e247748de023b 100644 --- a/src/Symfony/Component/Scheduler/Generator/MessageGenerator.php +++ b/src/Symfony/Component/Scheduler/Generator/MessageGenerator.php @@ -74,7 +74,15 @@ public function getMessages(): \Generator $yield = false; } - if ($nextTime = $trigger->getNextRunDate($time)) { + $nextTime = $trigger->getNextRunDate($time); + + if ($this->schedule->shouldProcessOnlyLastMissedRun()) { + while ($nextTime < $this->clock->now()) { + $nextTime = $trigger->getNextRunDate($nextTime); + } + } + + if ($nextTime) { $heap->insert([$nextTime, $index, $recurringMessage]); } diff --git a/src/Symfony/Component/Scheduler/Schedule.php b/src/Symfony/Component/Scheduler/Schedule.php index c23bfb9e0716a..1da3db35aad1f 100644 --- a/src/Symfony/Component/Scheduler/Schedule.php +++ b/src/Symfony/Component/Scheduler/Schedule.php @@ -31,6 +31,7 @@ public function __construct( private ?LockInterface $lock = null; private ?CacheInterface $state = null; private bool $shouldRestart = false; + private bool $onlyLastMissed = false; public function with(RecurringMessage $message, RecurringMessage ...$messages): static { @@ -123,6 +124,21 @@ public function getState(): ?CacheInterface return $this->state; } + /** + * @return $this + */ + public function processOnlyLastMissedRun(bool $onlyLastMissed): static + { + $this->onlyLastMissed = $onlyLastMissed; + + return $this; + } + + public function shouldProcessOnlyLastMissedRun(): bool + { + return $this->onlyLastMissed; + } + /** * @return array */ diff --git a/src/Symfony/Component/Scheduler/Tests/Generator/MessageGeneratorTest.php b/src/Symfony/Component/Scheduler/Tests/Generator/MessageGeneratorTest.php index 9f132108ad2da..43d966c4ffcf7 100644 --- a/src/Symfony/Component/Scheduler/Tests/Generator/MessageGeneratorTest.php +++ b/src/Symfony/Component/Scheduler/Tests/Generator/MessageGeneratorTest.php @@ -204,6 +204,67 @@ public function testCheckpointSavedInBrokenLoop() $this->assertEquals(self::makeDateTime('22:13:00'), $checkpoint->time()); } + public function testCheckpointSavedInBigBrokenLoop() + { + $clock = new MockClock(self::makeDateTime('22:15:00')); + + $message = RecurringMessage::every('1 minute', (object) ['id' => 'message']); + $schedule = (new Schedule())->add($message); + + $cache = new ArrayAdapter(); + $schedule->stateful($cache); + $checkpoint = new Checkpoint('dummy', cache: $cache); + + $scheduler = new MessageGenerator($schedule, 'dummy', clock: $clock, checkpoint: $checkpoint); + + // Warmup. The first run is always returns nothing. + $this->assertSame([], iterator_to_array($scheduler->getMessages(), false)); + $this->assertEquals(self::makeDateTime('22:15:00'), $checkpoint->time()); + + $clock->sleep(60 + 10); // 22:16:10 + + $this->assertCount(1, iterator_to_array($scheduler->getMessages(), false)); + + $clock->sleep(2 * 60); // 22:18:10 + + $this->assertCount(2, iterator_to_array($scheduler->getMessages(), false)); + + $clock->sleep(5 * 60); // 22:23:10 + + $this->assertCount(5, iterator_to_array($scheduler->getMessages(), false)); + + $this->assertEquals(self::makeDateTime('22:23:00'), $checkpoint->time()); + } + + public function testCheckpointSavedInBigBrokenLoopWithOnlyLastMissed() + { + $clock = new MockClock(self::makeDateTime('22:15:00')); + + $message = RecurringMessage::every('1 minute', (object) ['id' => 'message']); + $schedule = (new Schedule())->add($message); + + $cache = new ArrayAdapter(); + $schedule->stateful($cache)->processOnlyLastMissedRun(true); + $checkpoint = new Checkpoint('dummy', cache: $cache); + + $scheduler = new MessageGenerator($schedule, 'dummy', clock: $clock, checkpoint: $checkpoint); + + // Warmup. The first run is always returns nothing. + $this->assertSame([], iterator_to_array($scheduler->getMessages(), false)); + $this->assertEquals(self::makeDateTime('22:15:00'), $clock->now()); + + $clock->sleep(60 + 10); // 22:16:10 + $this->assertCount(1, iterator_to_array($scheduler->getMessages(), false)); + + $clock->sleep(2 * 60); // 22:18:10 + $this->assertCount(1, iterator_to_array($scheduler->getMessages(), false)); + + $clock->sleep(5 * 60); // 22:23:10 + $this->assertCount(1, iterator_to_array($scheduler->getMessages(), false)); + + $this->assertEquals(self::makeDateTime('22:23:10'), $clock->now()); + } + public static function messagesProvider(): \Generator { $first = (object) ['id' => 'first'];