@cleverbrush/scheduler 1.0.0-beta.2 → 1.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ScheduleCalculator.d.ts +10 -0
- package/dist/ScheduleCalculator.js +208 -0
- package/dist/ScheduleCalculator.js.map +1 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +360 -0
- package/dist/index.js.map +1 -0
- package/dist/jobRepository.d.ts +34 -0
- package/dist/jobRepository.js +90 -0
- package/dist/jobRepository.js.map +1 -0
- package/dist/types.d.ts +373 -0
- package/dist/types.js +124 -0
- package/dist/types.js.map +1 -0
- package/package.json +6 -2
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ScheduleCalculator = void 0;
|
|
4
|
+
const MS_IN_DAY = 1000 * 60 * 60 * 24;
|
|
5
|
+
const MS_IN_WEEK = MS_IN_DAY * 7;
|
|
6
|
+
const getDayOfWeek = (date) => {
|
|
7
|
+
const res = date.getUTCDay();
|
|
8
|
+
if (res === 0)
|
|
9
|
+
return 7;
|
|
10
|
+
return res;
|
|
11
|
+
};
|
|
12
|
+
const getNumberOfDaysInMonth = (date) => {
|
|
13
|
+
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0, 0, 0, 0, 0)).getDate();
|
|
14
|
+
};
|
|
15
|
+
class ScheduleCalculator {
|
|
16
|
+
#schedule;
|
|
17
|
+
#currentDate = new Date();
|
|
18
|
+
#hour = 9;
|
|
19
|
+
#minute = 0;
|
|
20
|
+
#maxRepeat = -1;
|
|
21
|
+
#repeatCount = 0;
|
|
22
|
+
#hasNext = false;
|
|
23
|
+
#next;
|
|
24
|
+
constructor(schedule) {
|
|
25
|
+
if (!schedule)
|
|
26
|
+
throw new Error('schedule is required');
|
|
27
|
+
this.#schedule = { ...schedule };
|
|
28
|
+
if (typeof schedule.startsOn !== 'undefined') {
|
|
29
|
+
this.#currentDate = schedule.startsOn;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
this.#schedule.startsOn = new Date();
|
|
33
|
+
this.#currentDate = this.#schedule.startsOn;
|
|
34
|
+
}
|
|
35
|
+
if (schedule.every !== 'minute') {
|
|
36
|
+
if (typeof schedule.hour === 'number') {
|
|
37
|
+
this.#hour = schedule.hour;
|
|
38
|
+
}
|
|
39
|
+
if (typeof schedule.minute === 'number') {
|
|
40
|
+
this.#minute = schedule.minute;
|
|
41
|
+
}
|
|
42
|
+
if (schedule.every === 'day' &&
|
|
43
|
+
new Date(Date.UTC(this.#currentDate.getUTCFullYear(), this.#currentDate.getUTCMonth(), this.#currentDate.getUTCDate(), this.#hour, this.#minute, 0, 0)).getTime() < this.#currentDate.getTime()) {
|
|
44
|
+
const date = new Date(Date.UTC(this.#currentDate.getUTCFullYear(), this.#currentDate.getUTCMonth(), this.#currentDate.getUTCDate() + 1, this.#hour, this.#minute, 0, 0));
|
|
45
|
+
this.#currentDate = date;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (typeof schedule.maxOccurences === 'number') {
|
|
49
|
+
this.#maxRepeat = schedule.maxOccurences;
|
|
50
|
+
}
|
|
51
|
+
const next = this.#getNext();
|
|
52
|
+
if (typeof next !== 'undefined') {
|
|
53
|
+
this.#next = next;
|
|
54
|
+
this.#hasNext = true;
|
|
55
|
+
}
|
|
56
|
+
let leftToSkip = this.#schedule.startingFromIndex || 1;
|
|
57
|
+
while (leftToSkip-- > 1 && this.#hasNext) {
|
|
58
|
+
this.next();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
#getNext() {
|
|
62
|
+
let candidate = null;
|
|
63
|
+
let dayOfWeek;
|
|
64
|
+
switch (this.#schedule.every) {
|
|
65
|
+
case 'minute':
|
|
66
|
+
candidate =
|
|
67
|
+
this.#repeatCount === 0
|
|
68
|
+
? this.#schedule.startsOn
|
|
69
|
+
: new Date(Date.UTC(this.#currentDate.getUTCFullYear(), this.#currentDate.getUTCMonth(), this.#currentDate.getUTCDate(), this.#currentDate.getUTCHours(), this.#currentDate.getUTCMinutes() +
|
|
70
|
+
this.#schedule.interval, this.#currentDate.getUTCSeconds(), this.#currentDate.getUTCMilliseconds()));
|
|
71
|
+
break;
|
|
72
|
+
case 'day':
|
|
73
|
+
{
|
|
74
|
+
const date = new Date(this.#currentDate.getTime() +
|
|
75
|
+
MS_IN_DAY *
|
|
76
|
+
(this.#repeatCount === 0
|
|
77
|
+
? 0
|
|
78
|
+
: this.#schedule.interval - 1));
|
|
79
|
+
candidate = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), this.#hour, this.#minute, 0, 0));
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
case 'week':
|
|
83
|
+
{
|
|
84
|
+
let date = this.#currentDate;
|
|
85
|
+
do {
|
|
86
|
+
let found = false;
|
|
87
|
+
dayOfWeek = getDayOfWeek(date);
|
|
88
|
+
if (Number.isNaN(dayOfWeek))
|
|
89
|
+
return;
|
|
90
|
+
for (let i = 0; i < 7 - dayOfWeek + 1; i++) {
|
|
91
|
+
date = new Date(date.getTime() + (i == 0 ? 0 : MS_IN_DAY));
|
|
92
|
+
if (this.#schedule.endsOn &&
|
|
93
|
+
date > this.#schedule.endsOn) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (this.#schedule.dayOfWeek.includes(getDayOfWeek(date))) {
|
|
97
|
+
const dateWithTime = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), this.#hour, this.#minute, 0, 0));
|
|
98
|
+
if (dateWithTime > this.#schedule.startsOn) {
|
|
99
|
+
candidate = dateWithTime;
|
|
100
|
+
found = true;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (found)
|
|
106
|
+
break;
|
|
107
|
+
date = new Date(date.getTime() +
|
|
108
|
+
MS_IN_DAY +
|
|
109
|
+
(this.#repeatCount == 0
|
|
110
|
+
? 0
|
|
111
|
+
: (this.#schedule.interval - 1) *
|
|
112
|
+
MS_IN_WEEK));
|
|
113
|
+
} while ((this.#schedule.endsOn &&
|
|
114
|
+
date <= this.#schedule.endsOn) ||
|
|
115
|
+
!this.#schedule.endsOn);
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
case 'month':
|
|
119
|
+
{
|
|
120
|
+
let dateTime;
|
|
121
|
+
const cDate = this.#currentDate;
|
|
122
|
+
let iteration = 0;
|
|
123
|
+
do {
|
|
124
|
+
const date = this.#schedule.day === 'last'
|
|
125
|
+
? getNumberOfDaysInMonth(new Date(Date.UTC(cDate.getUTCFullYear(), cDate.getUTCMonth() +
|
|
126
|
+
iteration *
|
|
127
|
+
(this.#repeatCount === 0
|
|
128
|
+
? 1
|
|
129
|
+
: this.#schedule
|
|
130
|
+
.interval), 1, 0, 0, 0, 0)))
|
|
131
|
+
: this.#schedule.day;
|
|
132
|
+
dateTime = new Date(Date.UTC(cDate.getUTCFullYear(), cDate.getUTCMonth() +
|
|
133
|
+
iteration *
|
|
134
|
+
(this.#repeatCount === 0
|
|
135
|
+
? 1
|
|
136
|
+
: this.#schedule.interval), date, this.#hour, this.#minute, 0, 0));
|
|
137
|
+
iteration++;
|
|
138
|
+
} while (dateTime < this.#schedule.startsOn ||
|
|
139
|
+
dateTime <= this.#currentDate);
|
|
140
|
+
candidate = dateTime;
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
case 'year':
|
|
144
|
+
{
|
|
145
|
+
let dateTime;
|
|
146
|
+
const cDate = this.#currentDate;
|
|
147
|
+
let iteration = 0;
|
|
148
|
+
do {
|
|
149
|
+
const date = this.#schedule.day === 'last'
|
|
150
|
+
? getNumberOfDaysInMonth(new Date(Date.UTC(cDate.getUTCFullYear() +
|
|
151
|
+
iteration *
|
|
152
|
+
(this.#repeatCount === 0
|
|
153
|
+
? 1
|
|
154
|
+
: this.#schedule
|
|
155
|
+
.interval), this.#schedule.month - 1, 1, 0, 0, 0, 0)))
|
|
156
|
+
: this.#schedule.day;
|
|
157
|
+
dateTime = new Date(Date.UTC(cDate.getUTCFullYear() +
|
|
158
|
+
iteration *
|
|
159
|
+
(this.#repeatCount === 0
|
|
160
|
+
? 1
|
|
161
|
+
: this.#schedule.interval), this.#schedule.month - 1, date, this.#hour, this.#minute, 0, 0));
|
|
162
|
+
iteration++;
|
|
163
|
+
} while (dateTime < this.#schedule.startsOn ||
|
|
164
|
+
dateTime <= this.#currentDate);
|
|
165
|
+
candidate = dateTime;
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
default:
|
|
169
|
+
throw new Error('unknown schedule type');
|
|
170
|
+
}
|
|
171
|
+
if (!candidate)
|
|
172
|
+
return;
|
|
173
|
+
if (typeof this.#schedule.endsOn !== 'undefined' &&
|
|
174
|
+
candidate > this.#schedule.endsOn) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
return candidate;
|
|
178
|
+
}
|
|
179
|
+
hasNext(span) {
|
|
180
|
+
if (!this.#hasNext) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
if (typeof span !== 'number')
|
|
184
|
+
return this.#hasNext;
|
|
185
|
+
return this.#next.getTime() - new Date().getTime() <= span;
|
|
186
|
+
}
|
|
187
|
+
next() {
|
|
188
|
+
if (!this.#hasNext)
|
|
189
|
+
throw new Error('schedule is over');
|
|
190
|
+
const result = this.#next;
|
|
191
|
+
this.#currentDate = new Date(result.getTime() +
|
|
192
|
+
(['day', 'week'].includes(this.#schedule.every) ? MS_IN_DAY : 0));
|
|
193
|
+
this.#repeatCount++;
|
|
194
|
+
const next = this.#getNext();
|
|
195
|
+
this.#next = next;
|
|
196
|
+
this.#hasNext = typeof next !== 'undefined';
|
|
197
|
+
if (this.#maxRepeat > 0 && this.#repeatCount >= this.#maxRepeat) {
|
|
198
|
+
this.#next = undefined;
|
|
199
|
+
this.#hasNext = false;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
date: result,
|
|
203
|
+
index: this.#repeatCount
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
exports.ScheduleCalculator = ScheduleCalculator;
|
|
208
|
+
//# sourceMappingURL=ScheduleCalculator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScheduleCalculator.js","sourceRoot":"","sources":["../src/ScheduleCalculator.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
import { Readable } from 'stream';
|
|
5
|
+
import { JobSchedulerProps, CreateJobRequest, SchedulerStatus, Schedule, Job, JobInstance, JobInstanceStatus, Schemas } from './types.js';
|
|
6
|
+
import { ScheduleCalculator } from './ScheduleCalculator.js';
|
|
7
|
+
import { IJobRepository } from './jobRepository.js';
|
|
8
|
+
export { ScheduleCalculator, Schedule as TaskSchedule, Schemas };
|
|
9
|
+
declare type WorkerResult = {
|
|
10
|
+
status: JobInstanceStatus;
|
|
11
|
+
exitCode: number;
|
|
12
|
+
error?: Error;
|
|
13
|
+
};
|
|
14
|
+
declare type JobStartItem = {
|
|
15
|
+
jobId: string;
|
|
16
|
+
instanceId: number;
|
|
17
|
+
stdout: Readable;
|
|
18
|
+
stderr: Readable;
|
|
19
|
+
startDate: Date;
|
|
20
|
+
};
|
|
21
|
+
declare type JobEndItem = JobStartItem & {
|
|
22
|
+
endDate: Date;
|
|
23
|
+
};
|
|
24
|
+
declare type JobErrorItem = JobStartItem & {
|
|
25
|
+
endDate: Date;
|
|
26
|
+
error?: Error;
|
|
27
|
+
};
|
|
28
|
+
declare type Events = {
|
|
29
|
+
'job:start': (job: JobStartItem) => any;
|
|
30
|
+
'job:end': (job: JobEndItem) => any;
|
|
31
|
+
'job:error': (job: JobErrorItem) => any;
|
|
32
|
+
'job:timeout': (job: JobErrorItem) => any;
|
|
33
|
+
};
|
|
34
|
+
interface IJobScheduler {
|
|
35
|
+
on<T extends keyof Events>(name: T, callback: Events[T]): this;
|
|
36
|
+
addJob(job: CreateJobRequest): Promise<void>;
|
|
37
|
+
removeJob(id: string): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
export declare class JobScheduler extends EventEmitter implements IJobScheduler {
|
|
40
|
+
protected _rootFolder: string;
|
|
41
|
+
protected _status: SchedulerStatus;
|
|
42
|
+
protected _defaultTimezone: string;
|
|
43
|
+
protected _checkTimer: any;
|
|
44
|
+
protected _jobsRepository: IJobRepository;
|
|
45
|
+
protected _jobProps: Map<string, any>;
|
|
46
|
+
get status(): SchedulerStatus;
|
|
47
|
+
protected set status(val: SchedulerStatus);
|
|
48
|
+
private scheduleCalculatorCache;
|
|
49
|
+
protected getJobSchedule(job: Job): Promise<ScheduleCalculator>;
|
|
50
|
+
protected runWorkerWithTimeout(file: string, props: any, timeout: number): {
|
|
51
|
+
promise: Promise<WorkerResult>;
|
|
52
|
+
stderr: Readable;
|
|
53
|
+
stdout: Readable;
|
|
54
|
+
};
|
|
55
|
+
private readToEnd;
|
|
56
|
+
protected startJobInstance(instance: JobInstance): Promise<void>;
|
|
57
|
+
protected scheduleJobTo(job: Job, date: Date, index: number): Promise<JobInstance | null>;
|
|
58
|
+
protected checkForUpcomingJobs(): Promise<void>;
|
|
59
|
+
start(): Promise<void>;
|
|
60
|
+
stop(): void;
|
|
61
|
+
jobExists(jobId: string): Promise<boolean>;
|
|
62
|
+
removeJob(jobId: string): Promise<void>;
|
|
63
|
+
addJob(job: CreateJobRequest): Promise<void>;
|
|
64
|
+
constructor(props: JobSchedulerProps);
|
|
65
|
+
on<T extends keyof Events>(name: T, callback: Events[T]): this;
|
|
66
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.JobScheduler = exports.Schemas = exports.ScheduleCalculator = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const events_1 = require("events");
|
|
9
|
+
const stream_1 = require("stream");
|
|
10
|
+
const promises_1 = require("fs/promises");
|
|
11
|
+
const path_1 = require("path");
|
|
12
|
+
const worker_threads_1 = require("worker_threads");
|
|
13
|
+
const types_js_1 = require("./types.js");
|
|
14
|
+
Object.defineProperty(exports, "Schemas", { enumerable: true, get: function () { return types_js_1.Schemas; } });
|
|
15
|
+
const ScheduleCalculator_js_1 = require("./ScheduleCalculator.js");
|
|
16
|
+
Object.defineProperty(exports, "ScheduleCalculator", { enumerable: true, get: function () { return ScheduleCalculator_js_1.ScheduleCalculator; } });
|
|
17
|
+
const jobRepository_js_1 = require("./jobRepository.js");
|
|
18
|
+
const MAX_BUFFER_SIZE = 10 * 1024 * 1024; // 10MB
|
|
19
|
+
const CHECK_INTERVAL = 1000 * 10; // every 10 seconds
|
|
20
|
+
// const SCHEDULE_JOB_SPAN = 1000 * 60 * 60; // 1 hour
|
|
21
|
+
const SCHEDULE_JOB_SPAN = 1000 * 60; // 1 hour
|
|
22
|
+
const DEFAULT_JOB_TIMEOUT = 1000 * 20; // 20 seconds
|
|
23
|
+
const DEFAULT_MAX_CONSEQUENT_FAILS = 3;
|
|
24
|
+
class JobScheduler extends events_1.EventEmitter {
|
|
25
|
+
_rootFolder;
|
|
26
|
+
_status = 'stopped';
|
|
27
|
+
_defaultTimezone;
|
|
28
|
+
_checkTimer;
|
|
29
|
+
_jobsRepository = new jobRepository_js_1.InMemoryJobRepository();
|
|
30
|
+
_jobProps = new Map();
|
|
31
|
+
get status() {
|
|
32
|
+
return this._status;
|
|
33
|
+
}
|
|
34
|
+
set status(val) {
|
|
35
|
+
if (val === this._status)
|
|
36
|
+
return;
|
|
37
|
+
this._status = val;
|
|
38
|
+
}
|
|
39
|
+
scheduleCalculatorCache = new Map();
|
|
40
|
+
async getJobSchedule(job) {
|
|
41
|
+
if (this.scheduleCalculatorCache.has(job.id)) {
|
|
42
|
+
return this.scheduleCalculatorCache.get(job.id);
|
|
43
|
+
}
|
|
44
|
+
const schedule = {
|
|
45
|
+
...job.schedule
|
|
46
|
+
};
|
|
47
|
+
if (typeof job.firstInstanceEndedAt !== 'undefined') {
|
|
48
|
+
schedule.startsOn = job.firstInstanceEndedAt;
|
|
49
|
+
}
|
|
50
|
+
if (typeof job.successfullTimesRunned === 'number') {
|
|
51
|
+
schedule.startingFromIndex = job.successfullTimesRunned + 1;
|
|
52
|
+
}
|
|
53
|
+
const res = new ScheduleCalculator_js_1.ScheduleCalculator(schedule);
|
|
54
|
+
this.scheduleCalculatorCache.set(job.id, res);
|
|
55
|
+
return res;
|
|
56
|
+
}
|
|
57
|
+
runWorkerWithTimeout(file, props, timeout) {
|
|
58
|
+
const worker = new worker_threads_1.Worker(file, {
|
|
59
|
+
workerData: props,
|
|
60
|
+
execArgv: ['--unhandled-rejections=strict'],
|
|
61
|
+
stderr: true,
|
|
62
|
+
stdout: true
|
|
63
|
+
});
|
|
64
|
+
const promise = new Promise((resolve) => {
|
|
65
|
+
let timedOut = false;
|
|
66
|
+
let isFinished = false;
|
|
67
|
+
let error;
|
|
68
|
+
const timeoutTimer = setTimeout(() => {
|
|
69
|
+
if (isFinished)
|
|
70
|
+
return;
|
|
71
|
+
timedOut = true;
|
|
72
|
+
worker.terminate();
|
|
73
|
+
resolve({
|
|
74
|
+
exitCode: 1,
|
|
75
|
+
status: 'timedout'
|
|
76
|
+
});
|
|
77
|
+
}, timeout);
|
|
78
|
+
worker.on('error', (e) => {
|
|
79
|
+
error = e;
|
|
80
|
+
});
|
|
81
|
+
worker.on('exit', (exitCode) => {
|
|
82
|
+
if (isFinished)
|
|
83
|
+
return;
|
|
84
|
+
if (timedOut)
|
|
85
|
+
return;
|
|
86
|
+
clearTimeout(timeoutTimer);
|
|
87
|
+
isFinished = true;
|
|
88
|
+
resolve({
|
|
89
|
+
status: exitCode === 0 ? 'succeeded' : 'errored',
|
|
90
|
+
exitCode,
|
|
91
|
+
error
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
promise,
|
|
97
|
+
stderr: worker.stderr,
|
|
98
|
+
stdout: worker.stdout
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
readToEnd(source) {
|
|
102
|
+
return new Promise((res, rej) => {
|
|
103
|
+
const chunks = [];
|
|
104
|
+
source.on('data', (chunk) => chunks.push(chunk));
|
|
105
|
+
source.on('end', () => res(Buffer.concat(chunks)));
|
|
106
|
+
source.on('error', (err) => rej(err));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async startJobInstance(instance) {
|
|
110
|
+
const startDate = new Date();
|
|
111
|
+
instance = await this._jobsRepository.saveInstance({
|
|
112
|
+
...instance,
|
|
113
|
+
status: 'running',
|
|
114
|
+
startDate
|
|
115
|
+
});
|
|
116
|
+
let status, exitCode;
|
|
117
|
+
try {
|
|
118
|
+
const job = await this._jobsRepository.getJobById(instance.jobId);
|
|
119
|
+
const fileName = (0, path_1.join)(this._rootFolder, job.path);
|
|
120
|
+
const props = this._jobProps.get(job.id);
|
|
121
|
+
let finalProps = typeof props === 'function' ? props() : props;
|
|
122
|
+
if (finalProps instanceof Promise) {
|
|
123
|
+
finalProps = await finalProps;
|
|
124
|
+
}
|
|
125
|
+
instance = await this._jobsRepository.saveInstance({
|
|
126
|
+
...instance,
|
|
127
|
+
status: 'running'
|
|
128
|
+
});
|
|
129
|
+
const { promise, stderr, stdout } = this.runWorkerWithTimeout(fileName, finalProps, job.timeout);
|
|
130
|
+
const stdOutPass = stdout.pipe(new stream_1.PassThrough({
|
|
131
|
+
highWaterMark: MAX_BUFFER_SIZE
|
|
132
|
+
}));
|
|
133
|
+
const stdErrPass = stderr.pipe(new stream_1.PassThrough({
|
|
134
|
+
highWaterMark: MAX_BUFFER_SIZE
|
|
135
|
+
}));
|
|
136
|
+
const stdOutForJobStart = stdout.pipe(new stream_1.PassThrough({
|
|
137
|
+
highWaterMark: MAX_BUFFER_SIZE
|
|
138
|
+
}));
|
|
139
|
+
const stdErrForJobStart = stderr.pipe(new stream_1.PassThrough({
|
|
140
|
+
highWaterMark: MAX_BUFFER_SIZE
|
|
141
|
+
}));
|
|
142
|
+
const stdOutForJobEnd = stdout.pipe(new stream_1.PassThrough({
|
|
143
|
+
highWaterMark: MAX_BUFFER_SIZE
|
|
144
|
+
}));
|
|
145
|
+
const stdErrForJobEnd = stderr.pipe(new stream_1.PassThrough({
|
|
146
|
+
highWaterMark: MAX_BUFFER_SIZE
|
|
147
|
+
}));
|
|
148
|
+
this.emit('job:start', {
|
|
149
|
+
instanceId: instance.id,
|
|
150
|
+
jobId: job.id,
|
|
151
|
+
stderr: stdErrForJobStart,
|
|
152
|
+
stdout: stdOutForJobStart,
|
|
153
|
+
startDate
|
|
154
|
+
});
|
|
155
|
+
const stdOutStr = (await this.readToEnd(stdOutPass)).toString();
|
|
156
|
+
const stdErrStr = (await this.readToEnd(stdErrPass)).toString();
|
|
157
|
+
const result = await promise;
|
|
158
|
+
status = result.status;
|
|
159
|
+
exitCode = result.exitCode;
|
|
160
|
+
const { error } = result;
|
|
161
|
+
const endDate = new Date();
|
|
162
|
+
switch (status) {
|
|
163
|
+
case 'errored':
|
|
164
|
+
this.emit('job:error', {
|
|
165
|
+
instanceId: instance.id,
|
|
166
|
+
jobId: job.id,
|
|
167
|
+
stderr: stdErrForJobEnd,
|
|
168
|
+
stdout: stdOutForJobEnd,
|
|
169
|
+
startDate,
|
|
170
|
+
endDate,
|
|
171
|
+
error
|
|
172
|
+
});
|
|
173
|
+
break;
|
|
174
|
+
case 'timedout':
|
|
175
|
+
this.emit('job:timeout', {
|
|
176
|
+
instanceId: instance.id,
|
|
177
|
+
jobId: job.id,
|
|
178
|
+
stderr: stdErrForJobEnd,
|
|
179
|
+
stdout: stdOutForJobEnd,
|
|
180
|
+
startDate,
|
|
181
|
+
endDate,
|
|
182
|
+
error
|
|
183
|
+
});
|
|
184
|
+
break;
|
|
185
|
+
default:
|
|
186
|
+
this.emit('job:end', {
|
|
187
|
+
instanceId: instance.id,
|
|
188
|
+
jobId: job.id,
|
|
189
|
+
stderr: stdErrForJobEnd,
|
|
190
|
+
stdout: stdOutForJobEnd,
|
|
191
|
+
startDate,
|
|
192
|
+
endDate
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
instance = await this._jobsRepository.saveInstance({
|
|
196
|
+
...instance,
|
|
197
|
+
status,
|
|
198
|
+
exitCode,
|
|
199
|
+
stdErr: stdErrStr,
|
|
200
|
+
stdOut: stdOutStr,
|
|
201
|
+
endDate
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
const job = {
|
|
206
|
+
...(await this._jobsRepository.getJobById(instance.jobId))
|
|
207
|
+
};
|
|
208
|
+
job.timesRunned++;
|
|
209
|
+
let shouldRetry = false;
|
|
210
|
+
const schedule = await this.getJobSchedule(job);
|
|
211
|
+
if (status !== 'succeeded') {
|
|
212
|
+
if (job.consequentFailsCount + 1 >= job.maxConsequentFails &&
|
|
213
|
+
job.maxConsequentFails > 0) {
|
|
214
|
+
job.status = 'disabled';
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
job.consequentFailsCount += 1;
|
|
218
|
+
shouldRetry = true;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
job.successfullTimesRunned++;
|
|
223
|
+
job.consequentFailsCount = 0;
|
|
224
|
+
if (!schedule.hasNext()) {
|
|
225
|
+
job.status = 'finished';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
await this._jobsRepository.saveJob(job);
|
|
229
|
+
if (shouldRetry) {
|
|
230
|
+
await this.startJobInstance(instance);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async scheduleJobTo(job, date, index) {
|
|
235
|
+
let timer;
|
|
236
|
+
try {
|
|
237
|
+
const now = new Date();
|
|
238
|
+
let interval = date.getTime() - now.getTime();
|
|
239
|
+
if (interval < 0) {
|
|
240
|
+
interval = 0;
|
|
241
|
+
}
|
|
242
|
+
const instance = await this._jobsRepository.addInstance(job.id, {
|
|
243
|
+
scheduledTo: date,
|
|
244
|
+
status: 'scheduled',
|
|
245
|
+
timeout: job.timeout,
|
|
246
|
+
index
|
|
247
|
+
});
|
|
248
|
+
timer = setTimeout(async () => {
|
|
249
|
+
const actualJob = await this._jobsRepository.getJobById(job.id);
|
|
250
|
+
if (!actualJob || actualJob.status !== 'active') {
|
|
251
|
+
instance.status = 'canceled';
|
|
252
|
+
await this._jobsRepository.saveInstance(instance);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
await this.startJobInstance(instance);
|
|
256
|
+
}, interval);
|
|
257
|
+
return instance;
|
|
258
|
+
}
|
|
259
|
+
catch (e) {
|
|
260
|
+
clearTimeout(timer);
|
|
261
|
+
// console.log(e);
|
|
262
|
+
// console.log('task failed!');
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
async checkForUpcomingJobs() {
|
|
267
|
+
const jobs = await this._jobsRepository.getJobs();
|
|
268
|
+
for (let i = 0; i < jobs.length; i++) {
|
|
269
|
+
if (jobs[i].status !== 'active')
|
|
270
|
+
continue;
|
|
271
|
+
const schedule = await this.getJobSchedule(jobs[i]);
|
|
272
|
+
if (schedule.hasNext()) {
|
|
273
|
+
const scheduledInstances = await this._jobsRepository.getInstancesWithStatus(jobs[i].id, 'scheduled');
|
|
274
|
+
while (schedule.hasNext(SCHEDULE_JOB_SPAN)) {
|
|
275
|
+
const { date: nextRun, index } = schedule.next();
|
|
276
|
+
if (nextRun < new Date())
|
|
277
|
+
continue;
|
|
278
|
+
const alreadyScheduled = scheduledInstances.find((i) => i.index === index);
|
|
279
|
+
if (alreadyScheduled)
|
|
280
|
+
continue;
|
|
281
|
+
await this.scheduleJobTo(jobs[i], nextRun, index);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
jobs[i].status = 'finished';
|
|
286
|
+
await this._jobsRepository.saveJob(jobs[i]);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async start() {
|
|
291
|
+
if (this._status === 'started') {
|
|
292
|
+
throw new Error('Scheduler is already started');
|
|
293
|
+
}
|
|
294
|
+
this.status = 'started';
|
|
295
|
+
this._checkTimer = setInterval(this.checkForUpcomingJobs.bind(this), CHECK_INTERVAL);
|
|
296
|
+
// TODO: add logic
|
|
297
|
+
}
|
|
298
|
+
stop() {
|
|
299
|
+
if (this._status === 'stopped') {
|
|
300
|
+
throw new Error('Scheduler is already stopped');
|
|
301
|
+
}
|
|
302
|
+
clearInterval(this._checkTimer);
|
|
303
|
+
this.status = 'stopped';
|
|
304
|
+
// TODO: add logic
|
|
305
|
+
}
|
|
306
|
+
async jobExists(jobId) {
|
|
307
|
+
return (await this._jobsRepository.getJobById(jobId)) !== null;
|
|
308
|
+
}
|
|
309
|
+
async removeJob(jobId) {
|
|
310
|
+
if (typeof jobId !== 'string' || !jobId) {
|
|
311
|
+
throw new Error('id is required');
|
|
312
|
+
}
|
|
313
|
+
await this._jobsRepository.removeJob(jobId);
|
|
314
|
+
}
|
|
315
|
+
async addJob(job) {
|
|
316
|
+
const validationResult = await types_js_1.schemaRegistry.schemas.Models.CreateJobRequest.validate(job);
|
|
317
|
+
if (!validationResult.valid) {
|
|
318
|
+
throw new Error(`Invalid CreateJobRequest: ${validationResult.errors?.join('; ')}`);
|
|
319
|
+
}
|
|
320
|
+
const path = (0, path_1.join)(this._rootFolder, job.path);
|
|
321
|
+
await (0, promises_1.access)(path, fs_1.default.constants.R_OK);
|
|
322
|
+
this._jobProps.set(job.id, job.props);
|
|
323
|
+
await this._jobsRepository.createJob({
|
|
324
|
+
id: job.id,
|
|
325
|
+
createdAt: new Date(),
|
|
326
|
+
schedule: job.schedule,
|
|
327
|
+
timeout: job.timeout || DEFAULT_JOB_TIMEOUT,
|
|
328
|
+
path: job.path,
|
|
329
|
+
consequentFailsCount: 0,
|
|
330
|
+
timesRunned: 0,
|
|
331
|
+
successfullTimesRunned: 0,
|
|
332
|
+
maxConsequentFails: typeof job.maxConsequentFails === 'number'
|
|
333
|
+
? job.maxConsequentFails
|
|
334
|
+
: DEFAULT_MAX_CONSEQUENT_FAILS
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
constructor(props) {
|
|
338
|
+
super();
|
|
339
|
+
if (typeof props.rootFolder !== 'string') {
|
|
340
|
+
throw new Error('rootFolder must be a string');
|
|
341
|
+
}
|
|
342
|
+
if (typeof props.defaultTimeZone === 'string') {
|
|
343
|
+
this._defaultTimezone = props.defaultTimeZone;
|
|
344
|
+
}
|
|
345
|
+
if (typeof props.persistRepository === 'object') {
|
|
346
|
+
this._jobsRepository = props.persistRepository;
|
|
347
|
+
}
|
|
348
|
+
this._rootFolder = props.rootFolder;
|
|
349
|
+
setInterval(() => {
|
|
350
|
+
this._jobsRepository.dumpJobs();
|
|
351
|
+
this._jobsRepository.dumpInstances();
|
|
352
|
+
}, 10 * 1000);
|
|
353
|
+
}
|
|
354
|
+
on(name, callback) {
|
|
355
|
+
super.on(name, callback);
|
|
356
|
+
return this;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
exports.JobScheduler = JobScheduler;
|
|
360
|
+
//# sourceMappingURL=index.js.map
|