From 51801026cbc0c4d024c281fd8a69b28d1efba402 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 8 Dec 2019 17:24:29 +0100 Subject: [PATCH] implementing task rescheduling on task complete --- backend/middlewares/AdminMWs.ts | 2 +- backend/model/interfaces/ITaskManager.ts | 2 +- backend/model/tasks/ITask.ts | 2 +- backend/model/tasks/Task.ts | 8 +- backend/model/tasks/TaskManager.ts | 90 ++++++++++--------- .../unit/model/tasks/TaskManager.spec.ts | 23 +++-- 6 files changed, 74 insertions(+), 53 deletions(-) diff --git a/backend/middlewares/AdminMWs.ts b/backend/middlewares/AdminMWs.ts index fbc45d17..e96ed188 100644 --- a/backend/middlewares/AdminMWs.ts +++ b/backend/middlewares/AdminMWs.ts @@ -473,7 +473,7 @@ export class AdminMWs { try { const id = req.params.id; const taskConfig: any = req.body.config; - ObjectManagers.getInstance().TaskManager.start(id, taskConfig); + ObjectManagers.getInstance().TaskManager.run(id, taskConfig); req.resultPipe = 'ok'; return next(); } catch (err) { diff --git a/backend/model/interfaces/ITaskManager.ts b/backend/model/interfaces/ITaskManager.ts index 6423b68e..0fe3baa8 100644 --- a/backend/model/interfaces/ITaskManager.ts +++ b/backend/model/interfaces/ITaskManager.ts @@ -3,7 +3,7 @@ import {TaskDTO} from '../../../common/entities/task/TaskDTO'; export interface ITaskManager { - start(taskId: string, config: any): void; + run(taskId: string, config: any): Promise; stop(taskId: string): void; diff --git a/backend/model/tasks/ITask.ts b/backend/model/tasks/ITask.ts index 90493b1b..226d9011 100644 --- a/backend/model/tasks/ITask.ts +++ b/backend/model/tasks/ITask.ts @@ -6,7 +6,7 @@ export interface ITask extends TaskDTO { Supported: boolean; Progress: TaskProgressDTO; - start(config: T): void; + start(config: T): Promise; stop(): void; } diff --git a/backend/model/tasks/Task.ts b/backend/model/tasks/Task.ts index a81b2e44..bfc03f2b 100644 --- a/backend/model/tasks/Task.ts +++ b/backend/model/tasks/Task.ts @@ -10,6 +10,7 @@ export abstract class Task implements ITask { protected progress: TaskProgressDTO = null; protected running = false; protected config: T; + protected prResolve: () => void; public abstract get Supported(): boolean; @@ -23,7 +24,7 @@ export abstract class Task implements ITask { return this.progress; } - public start(config: T): void { + public start(config: T): Promise { if (this.running === false && this.Supported) { Logger.info('[Task]', 'Running task: ' + this.Name); this.config = config; @@ -39,8 +40,12 @@ export abstract class Task implements ITask { this.running = true; this.init().catch(console.error); this.run(); + return new Promise((resolve) => { + this.prResolve = resolve; + }); } else { Logger.info('[Task]', 'Task already running: ' + this.Name); + return Promise.reject(); } } @@ -57,6 +62,7 @@ export abstract class Task implements ITask { private onFinish(): void { this.progress = null; Logger.info('[Task]', 'Task finished: ' + this.Name); + this.prResolve(); } private run() { diff --git a/backend/model/tasks/TaskManager.ts b/backend/model/tasks/TaskManager.ts index 5f1f88cc..3a564c60 100644 --- a/backend/model/tasks/TaskManager.ts +++ b/backend/model/tasks/TaskManager.ts @@ -10,7 +10,7 @@ const LOG_TAG = '[TaskManager]'; export class TaskManager implements ITaskManager { - protected timers: NodeJS.Timeout[] = []; + protected timers: { schedule: TaskScheduleDTO, timer: NodeJS.Timeout }[] = []; constructor() { this.runSchedules(); @@ -24,10 +24,10 @@ export class TaskManager implements ITaskManager { return m; } - start(taskName: string, config: T): void { + async run(taskName: string, config: T): Promise { const t = this.findTask(taskName); if (t) { - t.start(config); + await t.start(config); } else { Logger.warn(LOG_TAG, 'cannot find task to start:' + taskName); } @@ -50,30 +50,38 @@ export class TaskManager implements ITaskManager { } public stopSchedules(): void { - this.timers.forEach(timer => clearTimeout(timer)); + this.timers.forEach(t => clearTimeout(t.timer)); this.timers = []; } public runSchedules(): void { this.stopSchedules(); Logger.info(LOG_TAG, 'Running task schedules'); - Config.Server.tasks.scheduled.forEach(schedule => { - const nextDate = this.getDateFromSchedule(new Date(), schedule); - if (nextDate && nextDate.getTime() > Date.now()) { - Logger.debug(LOG_TAG, 'running schedule: ' + schedule.taskName + - ' at ' + nextDate.toLocaleString(undefined, {hour12: false})); + Config.Server.tasks.scheduled.forEach(s => this.runSchedule(s)); + } - const timer: NodeJS.Timeout = setTimeout(() => { - this.start(schedule.taskName, schedule.config); - this.timers = this.timers.filter(t => t !== timer); - }, nextDate.getTime() - Date.now()); - this.timers.push(timer); + protected getNextDayOfTheWeek(refDate: Date, dayOfWeek: number) { + const date = new Date(refDate); + date.setDate(refDate.getDate() + (dayOfWeek + 1 + 7 - refDate.getDay()) % 7); + if (date.getDay() === refDate.getDay()) { + return new Date(refDate); + } + date.setHours(0, 0, 0, 0); + return date; + } - } else { - Logger.debug(LOG_TAG, 'skipping schedule:' + schedule.taskName); - } + protected nextValidDate(date: Date, h: number, m: number, dayDiff: number): Date { - }); + date.setSeconds(0); + if (date.getHours() < h || (date.getHours() === h && date.getMinutes() < m)) { + date.setHours(h); + date.setMinutes(m); + } else { + date.setTime(date.getTime() + dayDiff); + date.setHours(h); + date.setMinutes(m); + } + return date; } protected getDateFromSchedule(refDate: Date, schedule: TaskScheduleDTO): Date { @@ -82,40 +90,18 @@ export class TaskManager implements ITaskManager { return new Date(schedule.trigger.time); case TaskTriggerType.periodic: - const nextValidHM = (date: Date, h: number, m: number, dayDiff: number): Date => { - - if (date.getHours() < h || (date.getHours() === h && date.getMinutes() <= m)) { - date.setHours(h); - date.setMinutes(m); - } else { - date.setTime(date.getTime() + dayDiff); - date.setHours(h); - date.setMinutes(m); - } - return date; - }; - - const getNextDayOfTheWeek = (dayOfWeek: number, h: number, m: number): Date => { - const date = new Date(refDate); - date.setDate(refDate.getDate() + (dayOfWeek + 1 + 7 - refDate.getDay()) % 7); - if (date.getDay() === refDate.getDay()) { - return new Date(refDate); - } - date.setHours(0, 0, 0, 0); - return date; - }; const hour = Math.floor(schedule.trigger.atTime / 1000 / (60 * 60)); const minute = (schedule.trigger.atTime / 1000 / 60) % 60; if (schedule.trigger.periodicity <= 6) { // Between Monday and Sunday - const nextRunDate = getNextDayOfTheWeek(schedule.trigger.periodicity, hour, minute); - return nextValidHM(nextRunDate, hour, minute, 7 * 24 * 60 * 60 * 1000); + const nextRunDate = this.getNextDayOfTheWeek(refDate, schedule.trigger.periodicity); + return this.nextValidDate(nextRunDate, hour, minute, 7 * 24 * 60 * 60 * 1000); } // every day - return nextValidHM(new Date(refDate), hour, minute, 24 * 60 * 60 * 1000); + return this.nextValidDate(new Date(refDate), hour, minute, 24 * 60 * 60 * 1000); } return null; } @@ -124,4 +110,22 @@ export class TaskManager implements ITaskManager { return this.getAvailableTasks().find(t => t.Name === taskName); } + private runSchedule(schedule: TaskScheduleDTO) { + const nextDate = this.getDateFromSchedule(new Date(), schedule); + if (nextDate && nextDate.getTime() > Date.now()) { + Logger.debug(LOG_TAG, 'running schedule: ' + schedule.taskName + + ' at ' + nextDate.toLocaleString(undefined, {hour12: false})); + + const timer: NodeJS.Timeout = setTimeout(async () => { + this.timers = this.timers.filter(t => t.timer !== timer); + await this.run(schedule.taskName, schedule.config); + this.runSchedule(schedule); + }, nextDate.getTime() - Date.now()); + this.timers.push({schedule: schedule, timer: timer}); + + } else { + Logger.debug(LOG_TAG, 'skipping schedule:' + schedule.taskName); + } + } + } diff --git a/test/backend/unit/model/tasks/TaskManager.spec.ts b/test/backend/unit/model/tasks/TaskManager.spec.ts index 46822cf4..811850ac 100644 --- a/test/backend/unit/model/tasks/TaskManager.spec.ts +++ b/test/backend/unit/model/tasks/TaskManager.spec.ts @@ -14,7 +14,7 @@ describe('TaskManager', () => { it('should get date from schedule', async () => { const tm = new TaskManagerSpec(); - const refDate = new Date(2019, 7, 18, 5, 10); // its a sunday + const refDate = new Date(2019, 7, 18, 5, 10, 10, 0); // its a sunday expect(tm.getDateFromSchedule(refDate, { @@ -22,7 +22,7 @@ describe('TaskManager', () => { type: TaskTriggerType.scheduled, time: (new Date(2019, 7, 18, 5, 10)).getTime() } - })).to.be.deep.equal((new Date(2019, 7, 18, 5, 10))); + })).to.be.deep.equal((new Date(2019, 7, 18, 5, 10, 0))); for (let dayOfWeek = 0; dayOfWeek < 7; ++dayOfWeek) { @@ -36,7 +36,7 @@ describe('TaskManager', () => { atTime: (h * 60 + m) * 60 * 1000, periodicity: dayOfWeek } - })).to.be.deep.equal((new Date(2019, 7, nextDay, h, m)), 'for day: ' + dayOfWeek); + })).to.be.deep.equal((new Date(2019, 7, nextDay, h, m, 0)), 'for day: ' + dayOfWeek); h = 2; m = 5; @@ -47,7 +47,18 @@ describe('TaskManager', () => { atTime: (h * 60 + m) * 60 * 1000, periodicity: dayOfWeek } - })).to.be.deep.equal((new Date(2019, 7, nextDay, h, m)), 'for day: ' + dayOfWeek); + })).to.be.deep.equal((new Date(2019, 7, nextDay, h, m, 0)), 'for day: ' + dayOfWeek); + + h = 5; + m = 10; + nextDay = 18 + dayOfWeek + 1; + expect(tm.getDateFromSchedule(refDate, { + trigger: { + type: TaskTriggerType.periodic, + atTime: (h * 60 + m) * 60 * 1000, + periodicity: dayOfWeek + } + })).to.be.deep.equal((new Date(2019, 7, nextDay, h, m, 0)), 'for day: ' + dayOfWeek); } { @@ -59,7 +70,7 @@ describe('TaskManager', () => { atTime: (h * 60 + m) * 60 * 1000, periodicity: 7 } - })).to.be.deep.equal((new Date(2019, 7, 18, h, m))); + })).to.be.deep.equal((new Date(2019, 7, 18, h, m, 0))); } { const h = 2; @@ -70,7 +81,7 @@ describe('TaskManager', () => { atTime: (h * 60 + m) * 60 * 1000, periodicity: 7 } - })).to.be.deep.equal((new Date(2019, 7, 19, h, m))); + })).to.be.deep.equal((new Date(2019, 7, 19, h, m, 0))); } });