Skip to content

Commit 3ad4e94

Browse files
committed
feature #58001 [Scheduler] Add capability to skip missed periodic tasks, only the last schedule will be called (eltharin)
This PR was squashed before being merged into the 7.2 branch. Discussion ---------- [Scheduler] Add capability to skip missed periodic tasks, only the last schedule will be called | Q | A | ------------- | --- | Branch? | 7.2 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | Fix #57858 | License | MIT Allow a Schedule to run only once reccuring messages when a worker break (with stateful enalbed) Commits ------- 9ae569b [Scheduler] Add capability to skip missed periodic tasks, only the last schedule will be called
2 parents 5d47bea + 9ae569b commit 3ad4e94

File tree

4 files changed

+91
-1
lines changed

4 files changed

+91
-1
lines changed

src/Symfony/Component/Scheduler/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.2
5+
---
6+
7+
* Add capability to skip missed periodic tasks, only the last schedule will be called
8+
49
6.4
510
---
611

src/Symfony/Component/Scheduler/Generator/MessageGenerator.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,15 @@ public function getMessages(): \Generator
7474
$yield = false;
7575
}
7676

77-
if ($nextTime = $trigger->getNextRunDate($time)) {
77+
$nextTime = $trigger->getNextRunDate($time);
78+
79+
if ($this->schedule->shouldProcessOnlyLastMissedRun()) {
80+
while ($nextTime < $this->clock->now()) {
81+
$nextTime = $trigger->getNextRunDate($nextTime);
82+
}
83+
}
84+
85+
if ($nextTime) {
7886
$heap->insert([$nextTime, $index, $recurringMessage]);
7987
}
8088

src/Symfony/Component/Scheduler/Schedule.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public function __construct(
3131
private ?LockInterface $lock = null;
3232
private ?CacheInterface $state = null;
3333
private bool $shouldRestart = false;
34+
private bool $onlyLastMissed = false;
3435

3536
public function with(RecurringMessage $message, RecurringMessage ...$messages): static
3637
{
@@ -123,6 +124,21 @@ public function getState(): ?CacheInterface
123124
return $this->state;
124125
}
125126

127+
/**
128+
* @return $this
129+
*/
130+
public function processOnlyLastMissedRun(bool $onlyLastMissed): static
131+
{
132+
$this->onlyLastMissed = $onlyLastMissed;
133+
134+
return $this;
135+
}
136+
137+
public function shouldProcessOnlyLastMissedRun(): bool
138+
{
139+
return $this->onlyLastMissed;
140+
}
141+
126142
/**
127143
* @return array<RecurringMessage>
128144
*/

src/Symfony/Component/Scheduler/Tests/Generator/MessageGeneratorTest.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,67 @@ public function testCheckpointSavedInBrokenLoop()
204204
$this->assertEquals(self::makeDateTime('22:13:00'), $checkpoint->time());
205205
}
206206

207+
public function testCheckpointSavedInBigBrokenLoop()
208+
{
209+
$clock = new MockClock(self::makeDateTime('22:15:00'));
210+
211+
$message = RecurringMessage::every('1 minute', (object) ['id' => 'message']);
212+
$schedule = (new Schedule())->add($message);
213+
214+
$cache = new ArrayAdapter();
215+
$schedule->stateful($cache);
216+
$checkpoint = new Checkpoint('dummy', cache: $cache);
217+
218+
$scheduler = new MessageGenerator($schedule, 'dummy', clock: $clock, checkpoint: $checkpoint);
219+
220+
// Warmup. The first run is always returns nothing.
221+
$this->assertSame([], iterator_to_array($scheduler->getMessages(), false));
222+
$this->assertEquals(self::makeDateTime('22:15:00'), $checkpoint->time());
223+
224+
$clock->sleep(60 + 10); // 22:16:10
225+
226+
$this->assertCount(1, iterator_to_array($scheduler->getMessages(), false));
227+
228+
$clock->sleep(2 * 60); // 22:18:10
229+
230+
$this->assertCount(2, iterator_to_array($scheduler->getMessages(), false));
231+
232+
$clock->sleep(5 * 60); // 22:23:10
233+
234+
$this->assertCount(5, iterator_to_array($scheduler->getMessages(), false));
235+
236+
$this->assertEquals(self::makeDateTime('22:23:00'), $checkpoint->time());
237+
}
238+
239+
public function testCheckpointSavedInBigBrokenLoopWithOnlyLastMissed()
240+
{
241+
$clock = new MockClock(self::makeDateTime('22:15:00'));
242+
243+
$message = RecurringMessage::every('1 minute', (object) ['id' => 'message']);
244+
$schedule = (new Schedule())->add($message);
245+
246+
$cache = new ArrayAdapter();
247+
$schedule->stateful($cache)->processOnlyLastMissedRun(true);
248+
$checkpoint = new Checkpoint('dummy', cache: $cache);
249+
250+
$scheduler = new MessageGenerator($schedule, 'dummy', clock: $clock, checkpoint: $checkpoint);
251+
252+
// Warmup. The first run is always returns nothing.
253+
$this->assertSame([], iterator_to_array($scheduler->getMessages(), false));
254+
$this->assertEquals(self::makeDateTime('22:15:00'), $clock->now());
255+
256+
$clock->sleep(60 + 10); // 22:16:10
257+
$this->assertCount(1, iterator_to_array($scheduler->getMessages(), false));
258+
259+
$clock->sleep(2 * 60); // 22:18:10
260+
$this->assertCount(1, iterator_to_array($scheduler->getMessages(), false));
261+
262+
$clock->sleep(5 * 60); // 22:23:10
263+
$this->assertCount(1, iterator_to_array($scheduler->getMessages(), false));
264+
265+
$this->assertEquals(self::makeDateTime('22:23:10'), $clock->now());
266+
}
267+
207268
public static function messagesProvider(): \Generator
208269
{
209270
$first = (object) ['id' => 'first'];

0 commit comments

Comments
 (0)