Skip to content

Commit

Permalink
Merge pull request #1870 from thomaschaaf/master
Browse files Browse the repository at this point in the history
feat: add waitForCompletion for cronjobs to disable concurrent running cronjobs
  • Loading branch information
kamilmysliwiec authored Jan 23, 2025
2 parents 9d53b7b + fa58ebe commit f59682a
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 0 deletions.
6 changes: 6 additions & 0 deletions lib/decorators/cron.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 74 additions & 0 deletions tests/e2e/cron-jobs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
16 changes: 16 additions & 0 deletions tests/src/cron.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CronJob } from 'cron';
@Injectable()
export class CronService {
callsCount = 0;
callsFinishedCount = 0;
dynamicCallsCount = 0;

constructor(private readonly schedulerRegistry: SchedulerRegistry) {}
Expand Down Expand Up @@ -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);
}
Expand Down

0 comments on commit f59682a

Please # to comment.