diff --git a/lib/decorators/cron.decorator.ts b/lib/decorators/cron.decorator.ts index a98a4059..ff560922 100644 --- a/lib/decorators/cron.decorator.ts +++ b/lib/decorators/cron.decorator.ts @@ -30,6 +30,12 @@ export type CronOptions = { */ unrefTimeout?: boolean; + /** + * If true, no additional instances of cronjob will run until the current onTick callback has completed. + * Any new scheduled executions that occur while the current cronjob is running will be skipped entirely. + */ + waitForCompletion?: boolean; + /** * This flag indicates whether the job will be executed at all. * @default false diff --git a/tests/e2e/cron-jobs.spec.ts b/tests/e2e/cron-jobs.spec.ts index ce0bf93a..071de19f 100644 --- a/tests/e2e/cron-jobs.spec.ts +++ b/tests/e2e/cron-jobs.spec.ts @@ -82,6 +82,80 @@ describe('Cron', () => { expect(job.running).toBe(false); }); + it(`should wait for "cron" to complete`, async () => { + // run every minute for 61 seconds + // 00:01:00 - 00:02:01 + // 00:02:00 - skipped + // 00:03:00 - 00:04:01 + // 00:04:00 - skipped + // 00:05:00 - 00:06:01 + + const service = app.get(CronService); + + await app.init(); + const registry = app.get(SchedulerRegistry); + const job = registry.getCronJob('WAIT_FOR_COMPLETION'); + deleteAllRegisteredJobsExceptOne(registry, 'WAIT_FOR_COMPLETION'); + + expect(job.running).toBe(true); + expect(service.callsCount).toEqual(0); + + await clock.tickAsync('01:00'); + // 00:01:00 + expect(service.callsCount).toEqual(1); + expect(service.callsFinishedCount).toEqual(0); + expect(job.lastDate()).toEqual(new Date('2020-01-01T00:01:00.000Z')); + + await clock.tickAsync('00:01'); + // 00:01:01 + expect(service.callsCount).toEqual(1); + expect(service.callsFinishedCount).toEqual(0); + + await clock.tickAsync('00:59'); + // 00:02:00 + expect(service.callsCount).toEqual(1); + expect(service.callsFinishedCount).toEqual(0); + + await clock.tickAsync('00:01'); + // 00:02:01 + expect(service.callsCount).toEqual(1); + expect(service.callsFinishedCount).toEqual(1); + expect(job.lastDate()).toEqual(new Date('2020-01-01T00:02:00.000Z')); + + await clock.tickAsync('00:59'); + // 00:03:00 + expect(service.callsCount).toEqual(2); + expect(service.callsFinishedCount).toEqual(1); + + await clock.tickAsync('00:01'); + // 00:03:01 + expect(service.callsCount).toEqual(2); + expect(service.callsFinishedCount).toEqual(1); + expect(job.lastDate()).toEqual(new Date('2020-01-01T00:03:00.000Z')); + + await clock.tickAsync('00:59'); + // 00:04:00 + expect(service.callsCount).toEqual(2); + expect(service.callsFinishedCount).toEqual(1); + + await clock.tickAsync('00:01'); + // 00:04:01 + expect(service.callsCount).toEqual(2); + expect(service.callsFinishedCount).toEqual(2); + expect(job.lastDate()).toEqual(new Date('2020-01-01T00:04:00.000Z')); + + await clock.tickAsync('00:59'); + // 00:05:00 + expect(service.callsCount).toEqual(3); + expect(service.callsFinishedCount).toEqual(2); + + await clock.tickAsync('01:01'); + // 00:06:01 + expect(service.callsCount).toEqual(3); + expect(service.callsFinishedCount).toEqual(3); + expect(job.running).toBe(false); + }); + it(`should run "cron" 3 times every 60 seconds`, async () => { const service = app.get(CronService); diff --git a/tests/src/cron.service.ts b/tests/src/cron.service.ts index d32e9ee9..648fd5c4 100644 --- a/tests/src/cron.service.ts +++ b/tests/src/cron.service.ts @@ -7,6 +7,7 @@ import { CronJob } from 'cron'; @Injectable() export class CronService { callsCount = 0; + callsFinishedCount = 0; dynamicCallsCount = 0; constructor(private readonly schedulerRegistry: SchedulerRegistry) {} @@ -75,6 +76,21 @@ export class CronService { return job; } + @Cron(CronExpression.EVERY_MINUTE, { + name: 'WAIT_FOR_COMPLETION', + waitForCompletion: true, + }) + async handleLongRunningCron() { + ++this.callsCount; + await new Promise((r) => setTimeout(r, 61 * 1000)); + ++this.callsFinishedCount; + + if (this.callsCount > 2) { + const ref = this.schedulerRegistry.getCronJob('WAIT_FOR_COMPLETION'); + ref!.stop(); + } + } + doesExist(name: string): boolean { return this.schedulerRegistry.doesExist('cron', name); }