diff --git a/src/lib/features/scheduler/scheduler-service.test.ts b/src/lib/features/scheduler/scheduler-service.test.ts index adc8b582252..6d2ecaa9527 100644 --- a/src/lib/features/scheduler/scheduler-service.test.ts +++ b/src/lib/features/scheduler/scheduler-service.test.ts @@ -72,7 +72,7 @@ test('Schedules job immediately', async () => { const job = jest.fn(); - await schedulerService.schedule(job, 10, 'test-id'); + await schedulerService.schedule(job, 10, 'test-id', 0); expect(job).toBeCalledTimes(1); schedulerService.stop(); @@ -172,7 +172,7 @@ test('Can handle crash of a async job', async () => { await Promise.reject('async reason'); }; - await schedulerService.schedule(job, 50, 'test-id-10'); + await schedulerService.schedule(job, 50, 'test-id-10', 0); await ms(75); schedulerService.stop(); @@ -236,3 +236,29 @@ it('should emit scheduler job time event when scheduled function is run', async await schedulerService.schedule(mockJob, 50, 'testJobId'); await eventPromise; }); + +test('Delays initial job execution by jitter duration', async () => { + const { schedulerService } = createSchedulerTestService(); + + const job = jest.fn(); + const jitterMs = 10; + + await schedulerService.schedule(job, 10000, 'test-id', jitterMs); + expect(job).toBeCalledTimes(0); + + await ms(50); + expect(job).toBeCalledTimes(1); + schedulerService.stop(); +}); + +test('Does not apply jitter if schedule interval is smaller than max jitter', async () => { + const { schedulerService } = createSchedulerTestService(); + + const job = jest.fn(); + + // default jitter 2s-30s + await schedulerService.schedule(job, 1000, 'test-id'); + expect(job).toBeCalledTimes(1); + + schedulerService.stop(); +}); diff --git a/src/lib/features/scheduler/scheduler-service.ts b/src/lib/features/scheduler/scheduler-service.ts index 9852d6d17e0..aa45dbe8e11 100644 --- a/src/lib/features/scheduler/scheduler-service.ts +++ b/src/lib/features/scheduler/scheduler-service.ts @@ -3,6 +3,19 @@ import { Logger, LogProvider } from '../../logger'; import { IMaintenanceStatus } from '../maintenance/maintenance-service'; import { SCHEDULER_JOB_TIME } from '../../metric-events'; +// returns between min and max seconds in ms +// when schedule interval is smaller than max jitter then no jitter +function randomJitter( + minMs: number, + maxMs: number, + scheduleIntervalMs: number, +): number { + if (scheduleIntervalMs < maxMs) { + return 0; + } + return Math.random() * (maxMs - minMs) + minMs; +} + export class SchedulerService { private intervalIds: NodeJS.Timeout[] = []; @@ -26,6 +39,7 @@ export class SchedulerService { scheduledFunction: () => void, timeMs: number, id: string, + jitter = randomJitter(2 * 1000, 30 * 1000, timeMs), ): Promise { const runScheduledFunctionWithEvent = async () => { const startTime = process.hrtime(); @@ -61,8 +75,15 @@ export class SchedulerService { try { const maintenanceMode = await this.maintenanceStatus.isMaintenanceMode(); + if (!maintenanceMode) { - await runScheduledFunctionWithEvent(); + if (jitter) { + setTimeout(() => { + runScheduledFunctionWithEvent(); + }, jitter); + } else { + await runScheduledFunctionWithEvent(); + } } } catch (e) { this.logger.error(`initial scheduled job failed | id: ${id}`, e);