Skip to content

Commit 29b393e

Browse files
fix: Prevent double scheduling events and ensure long-running events work correctly (#3561)
Fixes two issues with the `CronjobController`. We were double scheduling events that had not run since last boot since `#execute` also schedules the next call. Additionally we were not correctly scheduling events that run for longer than 24 hours, since the daily timer would re-calculate the execution date every day.
1 parent a9f5d76 commit 29b393e

File tree

2 files changed

+14
-60
lines changed

2 files changed

+14
-60
lines changed

packages/snaps-controllers/src/cronjob/CronjobController.test.ts

Lines changed: 4 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -275,19 +275,14 @@ describe('CronjobController', () => {
275275
},
276276
);
277277

278-
const secondCronjobController = new CronjobController({
279-
messenger: controllerMessenger,
280-
state: cronjobController.state,
281-
stateManager: getMockStateManager(),
282-
});
278+
expect(handleRequest).toHaveBeenCalledTimes(1);
283279

284-
secondCronjobController.init();
280+
jest.advanceTimersByTime(inMilliseconds(26, Duration.Hour));
285281

286282
await new Promise((resolve) => originalProcessNextTick(resolve));
287-
expect(handleRequest).toHaveBeenCalledTimes(1);
283+
expect(handleRequest).toHaveBeenCalledTimes(2);
288284

289285
cronjobController.destroy();
290-
secondCronjobController.destroy();
291286
});
292287

293288
it('does not schedule cronjob that is too far in the future', () => {
@@ -473,55 +468,6 @@ describe('CronjobController', () => {
473468
cronjobController.destroy();
474469
});
475470

476-
it('handles scheduled event close to current time gracefully', () => {
477-
const rootMessenger = getRootCronjobControllerMessenger();
478-
const controllerMessenger =
479-
getRestrictedCronjobControllerMessenger(rootMessenger);
480-
481-
const cronjobController = new CronjobController({
482-
messenger: controllerMessenger,
483-
stateManager: getMockStateManager(),
484-
state: {
485-
events: {
486-
foo: {
487-
id: 'foo',
488-
recurring: false,
489-
date: '2022-01-01T00:00:01.000Z',
490-
schedule: '2022-01-01T00:00:01.000Z',
491-
scheduledAt: new Date().toISOString(),
492-
snapId: MOCK_SNAP_ID,
493-
request: {
494-
method: 'handleEvent',
495-
params: ['p1'],
496-
},
497-
},
498-
},
499-
},
500-
});
501-
502-
rootMessenger.subscribe('CronjobController:stateChange', () => {
503-
jest.advanceTimersByTime(inMilliseconds(2, Duration.Second));
504-
});
505-
506-
cronjobController.init();
507-
508-
expect(rootMessenger.call).toHaveBeenNthCalledWith(
509-
1,
510-
'SnapController:handleRequest',
511-
{
512-
snapId: MOCK_SNAP_ID,
513-
origin: METAMASK_ORIGIN,
514-
handler: HandlerType.OnCronjob,
515-
request: {
516-
method: 'handleEvent',
517-
params: ['p1'],
518-
},
519-
},
520-
);
521-
522-
cronjobController.destroy();
523-
});
524-
525471
it('handles the `snapInstalled` event', () => {
526472
const rootMessenger = getRootCronjobControllerMessenger();
527473
const controllerMessenger =
@@ -663,7 +609,7 @@ describe('CronjobController', () => {
663609
foo: {
664610
id: 'foo',
665611
recurring: false,
666-
date: '2022-01-01T01:00:00Z',
612+
date: '2022-01-01T01:00Z',
667613
schedule: '2022-01-01T01:00Z',
668614
scheduledAt: new Date().toISOString(),
669615
snapId: MOCK_SNAP_ID,

packages/snaps-controllers/src/cronjob/CronjobController.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,8 +406,15 @@ export class CronjobController extends BaseController<
406406
* Get the next execution date for a given event and start a timer for it.
407407
*
408408
* @param event - The event to schedule.
409+
* @param next - Whether to schedule to the next date, otherwise will
410+
* schedule for existing date.
409411
*/
410-
#schedule(event: InternalBackgroundEvent) {
412+
#schedule(event: InternalBackgroundEvent, next = true) {
413+
if (!next) {
414+
this.#startTimer(event);
415+
return;
416+
}
417+
411418
const date = getExecutionDate(event.schedule);
412419
const { nextState } = this.update((state) => {
413420
state.events[event.id].date = date;
@@ -621,9 +628,10 @@ export class CronjobController extends BaseController<
621628
// immediately.
622629
if (event.recurring && eventDate <= now) {
623630
this.#execute(event);
631+
continue;
624632
}
625633

626-
this.#schedule(event);
634+
this.#schedule(event, false);
627635
}
628636
}
629637

0 commit comments

Comments
 (0)