fix(core): Scheduler tasks should not trigger on follower instances (#10507)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2024-08-22 11:46:13 +02:00 committed by GitHub
parent c8ab9b1f84
commit 3428f28a73
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 27 additions and 4 deletions

View file

@ -1,13 +1,24 @@
import { Service } from 'typedi';
import { CronJob } from 'cron';
import type { CronExpression, Workflow } from 'n8n-workflow';
import { InstanceSettings } from './InstanceSettings';
@Service()
export class ScheduledTaskManager {
constructor(private readonly instanceSettings: InstanceSettings) {}
readonly cronJobs = new Map<string, CronJob[]>();
registerCron(workflow: Workflow, cronExpression: CronExpression, onTick: () => void) {
const cronJob = new CronJob(cronExpression, onTick, undefined, true, workflow.timezone);
const cronJob = new CronJob(
cronExpression,
() => {
if (this.instanceSettings.isLeader) onTick();
},
undefined,
true,
workflow.timezone,
);
const cronJobsForWorkflow = this.cronJobs.get(workflow.id);
if (cronJobsForWorkflow) {
cronJobsForWorkflow.push(cronJob);

View file

@ -1,9 +1,11 @@
import type { Workflow } from 'n8n-workflow';
import { mock } from 'jest-mock-extended';
import type { InstanceSettings } from '@/InstanceSettings';
import { ScheduledTaskManager } from '@/ScheduledTaskManager';
describe('ScheduledTaskManager', () => {
const instanceSettings = mock<InstanceSettings>({ isLeader: true });
const workflow = mock<Workflow>({ timezone: 'GMT' });
const everyMinute = '0 * * * * *';
const onTick = jest.fn();
@ -13,7 +15,7 @@ describe('ScheduledTaskManager', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.useFakeTimers();
scheduledTaskManager = new ScheduledTaskManager();
scheduledTaskManager = new ScheduledTaskManager(instanceSettings);
});
it('should throw when workflow timezone is invalid', () => {
@ -41,6 +43,15 @@ describe('ScheduledTaskManager', () => {
expect(onTick).toHaveBeenCalledTimes(10);
});
it('should should not invoke on follower instances', async () => {
scheduledTaskManager = new ScheduledTaskManager(mock<InstanceSettings>({ isLeader: false }));
scheduledTaskManager.registerCron(workflow, everyMinute, onTick);
expect(onTick).not.toHaveBeenCalled();
jest.advanceTimersByTime(10 * 60 * 1000); // 10 minutes
expect(onTick).not.toHaveBeenCalled();
});
it('should deregister CronJobs for a workflow', async () => {
scheduledTaskManager.registerCron(workflow, everyMinute, onTick);
scheduledTaskManager.registerCron(workflow, everyMinute, onTick);

View file

@ -1,6 +1,6 @@
import * as n8nWorkflow from 'n8n-workflow';
import type { INode, ITriggerFunctions, Workflow } from 'n8n-workflow';
import { returnJsonArray } from 'n8n-core';
import { type InstanceSettings, returnJsonArray } from 'n8n-core';
import { ScheduledTaskManager } from 'n8n-core/dist/ScheduledTaskManager';
import { mock } from 'jest-mock-extended';
import { ScheduleTrigger } from '../ScheduleTrigger.node';
@ -18,7 +18,8 @@ describe('ScheduleTrigger', () => {
const node = mock<INode>({ typeVersion: 1 });
const workflow = mock<Workflow>({ timezone });
const scheduledTaskManager = new ScheduledTaskManager();
const instanceSettings = mock<InstanceSettings>({ isLeader: true });
const scheduledTaskManager = new ScheduledTaskManager(instanceSettings);
const helpers = mock<ITriggerFunctions['helpers']>({
returnJsonArray,
registerCron: (cronExpression, onTick) =>