@orion-js/dogs 4.5.1 → 4.6.2
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/index.cjs +448 -177
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +448 -177
- package/dist/index.js.map +1 -1
- package/dist/repos/JobsRepo.d.ts +23 -7
- package/dist/services/Executor.d.ts +5 -5
- package/dist/services/WorkerService.d.ts +46 -13
- package/dist/types/JobRecord.d.ts +8 -0
- package/dist/types/StartConfig.d.ts +4 -3
- package/dist/types/Worker.d.ts +6 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -71,6 +71,11 @@ var JobRecordSchema = schemaWithName("JobRecord", {
|
|
|
71
71
|
nextRunAt: { type: "date" },
|
|
72
72
|
lastRunAt: { type: "date", optional: true },
|
|
73
73
|
lockedUntil: { type: "date", optional: true },
|
|
74
|
+
/**
|
|
75
|
+
* Identifies the execution that currently owns the lock. Mutations from an execution must match
|
|
76
|
+
* this value so a stale execution cannot modify a newer claim.
|
|
77
|
+
*/
|
|
78
|
+
lockId: { type: "string", optional: true },
|
|
74
79
|
tries: { type: "number", optional: true },
|
|
75
80
|
params: { type: "blackbox", optional: true },
|
|
76
81
|
/**
|
|
@@ -93,6 +98,35 @@ var JobRecordSchema = schemaWithName("JobRecord", {
|
|
|
93
98
|
var DEFAULT_MAX_TRIES_REACHED_RETENTION_MS = 1e3 * 60 * 60 * 24 * 7;
|
|
94
99
|
|
|
95
100
|
// src/repos/JobsRepo.ts
|
|
101
|
+
var JOB_ACQUISITION_HINTS = {
|
|
102
|
+
byJobName: {
|
|
103
|
+
jobName: 1,
|
|
104
|
+
priority: -1,
|
|
105
|
+
nextRunAt: 1
|
|
106
|
+
},
|
|
107
|
+
globalPriority: {
|
|
108
|
+
priority: -1,
|
|
109
|
+
nextRunAt: 1
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
var INITIAL_JOB_ACQUISITION_HINT = "byJobName";
|
|
113
|
+
var CANDIDATE_JOB_ACQUISITION_HINT = "globalPriority";
|
|
114
|
+
var JOB_ACQUISITION_SORT = {
|
|
115
|
+
priority: -1,
|
|
116
|
+
nextRunAt: 1
|
|
117
|
+
};
|
|
118
|
+
function getJobAcquisitionSelector(jobNames, now) {
|
|
119
|
+
return {
|
|
120
|
+
jobName: { $in: jobNames },
|
|
121
|
+
nextRunAt: { $lte: now },
|
|
122
|
+
$and: [
|
|
123
|
+
{ $or: [{ lockedUntil: { $exists: false } }, { lockedUntil: { $lte: now } }] },
|
|
124
|
+
// Exclude event jobs that have reached max tries. Recurrent jobs keep running even
|
|
125
|
+
// if old records still have this status from previous versions.
|
|
126
|
+
{ $or: [{ type: { $ne: "event" } }, { status: { $ne: "maxTriesReached" } }] }
|
|
127
|
+
]
|
|
128
|
+
};
|
|
129
|
+
}
|
|
96
130
|
var _jobs_dec, _JobsRepo_decorators, _init;
|
|
97
131
|
_JobsRepo_decorators = [Repository()], _jobs_dec = [MongoCollection({
|
|
98
132
|
idGeneration: "uuid",
|
|
@@ -100,11 +134,10 @@ _JobsRepo_decorators = [Repository()], _jobs_dec = [MongoCollection({
|
|
|
100
134
|
schema: JobRecordSchema,
|
|
101
135
|
indexes: [
|
|
102
136
|
{
|
|
103
|
-
keys:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
137
|
+
keys: JOB_ACQUISITION_HINTS.byJobName
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
keys: JOB_ACQUISITION_HINTS.globalPriority
|
|
108
141
|
},
|
|
109
142
|
{
|
|
110
143
|
keys: {
|
|
@@ -137,29 +170,20 @@ var JobsRepo = class {
|
|
|
137
170
|
constructor() {
|
|
138
171
|
__publicField(this, "jobs", __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
|
|
139
172
|
}
|
|
140
|
-
async getJobAndLock(jobNames, lockTime) {
|
|
141
|
-
const
|
|
173
|
+
async getJobAndLock(jobNames, lockTime, hint = INITIAL_JOB_ACQUISITION_HINT) {
|
|
174
|
+
const executionId = generateId();
|
|
175
|
+
const now = /* @__PURE__ */ new Date();
|
|
176
|
+
const lockedUntil = new Date(now.getTime() + lockTime);
|
|
142
177
|
const job = await this.jobs.findOneAndUpdate(
|
|
178
|
+
getJobAcquisitionSelector(jobNames, now),
|
|
143
179
|
{
|
|
144
|
-
|
|
145
|
-
nextRunAt: { $lte: /* @__PURE__ */ new Date() },
|
|
146
|
-
$and: [
|
|
147
|
-
{ $or: [{ lockedUntil: { $exists: false } }, { lockedUntil: { $lte: /* @__PURE__ */ new Date() } }] },
|
|
148
|
-
// Exclude event jobs that have reached max tries. Recurrent jobs keep running even
|
|
149
|
-
// if old records still have this status from previous versions.
|
|
150
|
-
{ $or: [{ type: { $ne: "event" } }, { status: { $ne: "maxTriesReached" } }] }
|
|
151
|
-
]
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
$set: { lockedUntil, lastRunAt: /* @__PURE__ */ new Date() },
|
|
180
|
+
$set: { lockId: executionId, lockedUntil, lastRunAt: now },
|
|
155
181
|
$inc: { tries: 1 }
|
|
156
182
|
},
|
|
157
183
|
{
|
|
158
184
|
mongoOptions: {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
nextRunAt: 1
|
|
162
|
-
},
|
|
185
|
+
hint: JOB_ACQUISITION_HINTS[hint],
|
|
186
|
+
sort: JOB_ACQUISITION_SORT,
|
|
163
187
|
returnDocument: "before"
|
|
164
188
|
}
|
|
165
189
|
}
|
|
@@ -172,7 +196,8 @@ var JobsRepo = class {
|
|
|
172
196
|
}
|
|
173
197
|
return {
|
|
174
198
|
jobId: job._id,
|
|
175
|
-
executionId
|
|
199
|
+
executionId,
|
|
200
|
+
lockId: executionId,
|
|
176
201
|
name: job.jobName,
|
|
177
202
|
params: job.params,
|
|
178
203
|
type: job.type,
|
|
@@ -183,8 +208,24 @@ var JobsRepo = class {
|
|
|
183
208
|
wasStale
|
|
184
209
|
};
|
|
185
210
|
}
|
|
186
|
-
async
|
|
187
|
-
|
|
211
|
+
async getJobAcquisitionExecutionTime(jobNames, hint) {
|
|
212
|
+
var _a;
|
|
213
|
+
const explain = await this.jobs.find(getJobAcquisitionSelector(jobNames, /* @__PURE__ */ new Date()), {
|
|
214
|
+
hint: JOB_ACQUISITION_HINTS[hint],
|
|
215
|
+
maxTimeMS: 1e3,
|
|
216
|
+
readPreference: "primary"
|
|
217
|
+
}).sort(JOB_ACQUISITION_SORT).limit(1).explain("executionStats");
|
|
218
|
+
const executionTimeMillis = (_a = explain.executionStats) == null ? void 0 : _a.executionTimeMillis;
|
|
219
|
+
if (typeof executionTimeMillis !== "number" || !Number.isFinite(executionTimeMillis)) {
|
|
220
|
+
throw new Error(`Explain for acquisition hint "${hint}" returned no execution time`);
|
|
221
|
+
}
|
|
222
|
+
return executionTimeMillis;
|
|
223
|
+
}
|
|
224
|
+
async setJobRecordPriority(jobId, priority, lockId) {
|
|
225
|
+
const result = await this.jobs.updateOne(lockId ? { _id: jobId, lockId } : jobId, {
|
|
226
|
+
$set: { priority }
|
|
227
|
+
});
|
|
228
|
+
return result.matchedCount > 0;
|
|
188
229
|
}
|
|
189
230
|
async scheduleNextRun(options) {
|
|
190
231
|
const updator = {
|
|
@@ -193,22 +234,31 @@ var JobsRepo = class {
|
|
|
193
234
|
priority: options.priority,
|
|
194
235
|
...options.resetTries ? { tries: 0 } : {}
|
|
195
236
|
},
|
|
196
|
-
$unset: { lockedUntil: "" }
|
|
237
|
+
$unset: { lockId: "", lockedUntil: "" }
|
|
197
238
|
};
|
|
198
|
-
await this.jobs.updateOne(
|
|
239
|
+
const result = await this.jobs.updateOne(
|
|
240
|
+
options.lockId ? { _id: options.jobId, lockId: options.lockId } : options.jobId,
|
|
241
|
+
updator
|
|
242
|
+
);
|
|
243
|
+
return result.matchedCount > 0;
|
|
199
244
|
}
|
|
200
|
-
async deleteEventJob(jobId) {
|
|
201
|
-
await this.jobs.deleteOne({
|
|
245
|
+
async deleteEventJob(jobId, lockId) {
|
|
246
|
+
const result = await this.jobs.deleteOne({
|
|
247
|
+
_id: jobId,
|
|
248
|
+
type: "event",
|
|
249
|
+
...lockId ? { lockId } : {}
|
|
250
|
+
});
|
|
251
|
+
return result.deletedCount > 0;
|
|
202
252
|
}
|
|
203
253
|
/**
|
|
204
254
|
* Marks an event job as having reached its maximum tries limit.
|
|
205
255
|
* The job won't be picked up for execution and MongoDB will delete it after
|
|
206
256
|
* the configured retention period.
|
|
207
257
|
*/
|
|
208
|
-
async markJobAsMaxTriesReached(jobId, retentionMs = DEFAULT_MAX_TRIES_REACHED_RETENTION_MS) {
|
|
258
|
+
async markJobAsMaxTriesReached(jobId, retentionMs = DEFAULT_MAX_TRIES_REACHED_RETENTION_MS, lockId) {
|
|
209
259
|
const maxTriesReachedAt = /* @__PURE__ */ new Date();
|
|
210
|
-
await this.jobs.updateOne(
|
|
211
|
-
{ _id: jobId, type: "event" },
|
|
260
|
+
const result = await this.jobs.updateOne(
|
|
261
|
+
{ _id: jobId, type: "event", ...lockId ? { lockId } : {} },
|
|
212
262
|
{
|
|
213
263
|
$set: {
|
|
214
264
|
status: "maxTriesReached",
|
|
@@ -216,11 +266,13 @@ var JobsRepo = class {
|
|
|
216
266
|
...retentionMs === null ? {} : { expiresAt: new Date(maxTriesReachedAt.getTime() + retentionMs) }
|
|
217
267
|
},
|
|
218
268
|
$unset: {
|
|
269
|
+
lockId: "",
|
|
219
270
|
lockedUntil: "",
|
|
220
271
|
...retentionMs === null ? { expiresAt: "" } : {}
|
|
221
272
|
}
|
|
222
273
|
}
|
|
223
274
|
);
|
|
275
|
+
return result.matchedCount > 0;
|
|
224
276
|
}
|
|
225
277
|
/**
|
|
226
278
|
* Applies the current retention configuration to existing maxTriesReached
|
|
@@ -249,23 +301,22 @@ var JobsRepo = class {
|
|
|
249
301
|
}
|
|
250
302
|
]);
|
|
251
303
|
}
|
|
252
|
-
async extendLockTime(jobId, extraTime) {
|
|
253
|
-
|
|
304
|
+
async extendLockTime(jobId, extraTime, lockId) {
|
|
305
|
+
return this.updateLockTime(jobId, extraTime, lockId);
|
|
254
306
|
}
|
|
255
307
|
/**
|
|
256
308
|
* Updates the lock time for a job to the specified duration from now.
|
|
257
309
|
* Can be used to both extend or shorten the lock time.
|
|
258
310
|
*/
|
|
259
|
-
async updateLockTime(jobId, lockDuration) {
|
|
311
|
+
async updateLockTime(jobId, lockDuration, lockId) {
|
|
260
312
|
const lockedUntil = new Date(Date.now() + lockDuration);
|
|
261
|
-
await this.jobs.updateOne(
|
|
262
|
-
{
|
|
263
|
-
_id: jobId
|
|
264
|
-
},
|
|
313
|
+
const result = await this.jobs.updateOne(
|
|
314
|
+
{ _id: jobId, ...lockId ? { lockId } : {} },
|
|
265
315
|
{
|
|
266
316
|
$set: { lockedUntil }
|
|
267
317
|
}
|
|
268
318
|
);
|
|
319
|
+
return result.matchedCount > 0;
|
|
269
320
|
}
|
|
270
321
|
async unlockAllJobs() {
|
|
271
322
|
const result = await this.jobs.updateMany(
|
|
@@ -273,7 +324,7 @@ var JobsRepo = class {
|
|
|
273
324
|
lockedUntil: { $exists: true }
|
|
274
325
|
},
|
|
275
326
|
{
|
|
276
|
-
$unset: { lockedUntil: "" }
|
|
327
|
+
$unset: { lockId: "", lockedUntil: "" }
|
|
277
328
|
}
|
|
278
329
|
);
|
|
279
330
|
return result.modifiedCount;
|
|
@@ -579,11 +630,15 @@ var Executor = class {
|
|
|
579
630
|
getEffectiveLockTime(job, jobToRun) {
|
|
580
631
|
return job.lockTime ?? jobToRun.lockTime;
|
|
581
632
|
}
|
|
582
|
-
getContext(job, jobToRun, onStale) {
|
|
633
|
+
getContext(job, jobToRun, onStale, isExecutionActive) {
|
|
583
634
|
var _a;
|
|
584
635
|
const effectiveLockTime = this.getEffectiveLockTime(job, jobToRun);
|
|
585
636
|
let staleTimeout = setTimeout(() => onStale(), effectiveLockTime);
|
|
586
637
|
(_a = staleTimeout.unref) == null ? void 0 : _a.call(staleTimeout);
|
|
638
|
+
const contextLogger = logger3.addMetadata({
|
|
639
|
+
jobName: jobToRun.name,
|
|
640
|
+
jobId: jobToRun.jobId
|
|
641
|
+
});
|
|
587
642
|
return {
|
|
588
643
|
definition: job,
|
|
589
644
|
record: jobToRun,
|
|
@@ -591,15 +646,26 @@ var Executor = class {
|
|
|
591
646
|
clearStaleTimeout: () => clearTimeout(staleTimeout),
|
|
592
647
|
extendLockTime: async (extraTime) => {
|
|
593
648
|
var _a2;
|
|
649
|
+
if (!isExecutionActive()) {
|
|
650
|
+
contextLogger.warn(`Will not extend lock for stale execution "${jobToRun.executionId}"`);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
594
653
|
clearTimeout(staleTimeout);
|
|
654
|
+
let didExtend = false;
|
|
655
|
+
try {
|
|
656
|
+
didExtend = await this.jobsRepo.extendLockTime(jobToRun.jobId, extraTime, jobToRun.lockId);
|
|
657
|
+
} catch (error) {
|
|
658
|
+
onStale();
|
|
659
|
+
throw error;
|
|
660
|
+
}
|
|
661
|
+
if (!didExtend) {
|
|
662
|
+
onStale();
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
595
665
|
staleTimeout = setTimeout(() => onStale(), extraTime);
|
|
596
666
|
(_a2 = staleTimeout.unref) == null ? void 0 : _a2.call(staleTimeout);
|
|
597
|
-
await this.jobsRepo.extendLockTime(jobToRun.jobId, extraTime);
|
|
598
667
|
},
|
|
599
|
-
logger:
|
|
600
|
-
jobName: jobToRun.name,
|
|
601
|
-
jobId: jobToRun.jobId
|
|
602
|
-
})
|
|
668
|
+
logger: contextLogger
|
|
603
669
|
};
|
|
604
670
|
}
|
|
605
671
|
getJobDefinition(jobToRun, jobs) {
|
|
@@ -631,8 +697,16 @@ var Executor = class {
|
|
|
631
697
|
jobLogger.warn(
|
|
632
698
|
`Job "${jobToRun.name}" has exceeded max tries (${jobToRun.tries}). Marking as maxTriesReached.`
|
|
633
699
|
);
|
|
634
|
-
await this.jobsRepo.markJobAsMaxTriesReached(
|
|
635
|
-
|
|
700
|
+
const didMark = await this.jobsRepo.markJobAsMaxTriesReached(
|
|
701
|
+
jobToRun.jobId,
|
|
702
|
+
retentionMs,
|
|
703
|
+
jobToRun.lockId
|
|
704
|
+
);
|
|
705
|
+
if (!didMark) {
|
|
706
|
+
jobLogger.warn(`Could not mark job "${jobToRun.name}" because its lock is no longer owned`);
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
if (!onMaxTriesReached) return true;
|
|
636
710
|
try {
|
|
637
711
|
await onMaxTriesReached(jobToRun);
|
|
638
712
|
} catch (callbackError) {
|
|
@@ -640,30 +714,33 @@ var Executor = class {
|
|
|
640
714
|
error: callbackError
|
|
641
715
|
});
|
|
642
716
|
}
|
|
717
|
+
return true;
|
|
643
718
|
}
|
|
644
719
|
async onError(error, job, jobToRun, context) {
|
|
645
720
|
const scheduleRecurrent = async () => {
|
|
646
721
|
if (job.type === "recurrent") {
|
|
647
|
-
|
|
722
|
+
return this.jobsRepo.scheduleNextRun({
|
|
648
723
|
jobId: jobToRun.jobId,
|
|
649
724
|
nextRunAt: getNextRunDate(job),
|
|
650
725
|
resetTries: true,
|
|
651
|
-
priority: job.priority
|
|
726
|
+
priority: job.priority,
|
|
727
|
+
lockId: jobToRun.lockId
|
|
652
728
|
});
|
|
653
729
|
}
|
|
730
|
+
return true;
|
|
654
731
|
};
|
|
655
732
|
const handleRetry = async (nextRunAt) => {
|
|
656
|
-
|
|
733
|
+
return this.jobsRepo.scheduleNextRun({
|
|
657
734
|
jobId: jobToRun.jobId,
|
|
658
735
|
nextRunAt,
|
|
659
736
|
resetTries: false,
|
|
660
|
-
priority: job.type === "recurrent" ? job.priority : jobToRun.priority
|
|
737
|
+
priority: job.type === "recurrent" ? job.priority : jobToRun.priority,
|
|
738
|
+
lockId: jobToRun.lockId
|
|
661
739
|
});
|
|
662
740
|
};
|
|
663
741
|
if (!job.onError) {
|
|
664
742
|
context.logger.error(`Error executing job "${jobToRun.name}"`, { error });
|
|
665
|
-
|
|
666
|
-
return;
|
|
743
|
+
return scheduleRecurrent();
|
|
667
744
|
}
|
|
668
745
|
context.logger.info(`Error executing job "${jobToRun.name}"`, { error });
|
|
669
746
|
const result = await job.onError(
|
|
@@ -672,12 +749,12 @@ var Executor = class {
|
|
|
672
749
|
context
|
|
673
750
|
);
|
|
674
751
|
if (result.action === "dismiss") {
|
|
675
|
-
|
|
676
|
-
return;
|
|
752
|
+
return scheduleRecurrent();
|
|
677
753
|
}
|
|
678
754
|
if (result.action === "retry") {
|
|
679
|
-
|
|
755
|
+
return handleRetry(getNextRunDate(result));
|
|
680
756
|
}
|
|
757
|
+
return true;
|
|
681
758
|
}
|
|
682
759
|
async saveExecution(options) {
|
|
683
760
|
const { startedAt, status, errorMessage, result, job, jobToRun } = options;
|
|
@@ -707,19 +784,21 @@ var Executor = class {
|
|
|
707
784
|
async afterExecutionSuccess(job, jobToRun, context) {
|
|
708
785
|
if (job.type === "recurrent") {
|
|
709
786
|
context.logger.debug(`Scheduling next run for recurrent job "${jobToRun.name}"`);
|
|
710
|
-
|
|
787
|
+
return this.jobsRepo.scheduleNextRun({
|
|
711
788
|
jobId: jobToRun.jobId,
|
|
712
789
|
nextRunAt: getNextRunDate(job),
|
|
713
790
|
resetTries: true,
|
|
714
|
-
priority: job.priority
|
|
791
|
+
priority: job.priority,
|
|
792
|
+
lockId: jobToRun.lockId
|
|
715
793
|
});
|
|
716
794
|
}
|
|
717
795
|
if (job.type === "event") {
|
|
718
796
|
context.logger.debug(`Removing event job after success "${jobToRun.name}"`);
|
|
719
|
-
|
|
797
|
+
return this.jobsRepo.deleteEventJob(jobToRun.jobId, jobToRun.lockId);
|
|
720
798
|
}
|
|
799
|
+
return false;
|
|
721
800
|
}
|
|
722
|
-
async executeJob(config, jobToRun,
|
|
801
|
+
async executeJob(config, jobToRun, onExecutionStale) {
|
|
723
802
|
const job = this.getJobDefinition(jobToRun, config.jobs);
|
|
724
803
|
if (!job) return;
|
|
725
804
|
if (job.type === "event") {
|
|
@@ -735,33 +814,64 @@ var Executor = class {
|
|
|
735
814
|
}
|
|
736
815
|
const effectiveLockTime = this.getEffectiveLockTime(job, jobToRun);
|
|
737
816
|
if (effectiveLockTime !== jobToRun.lockTime) {
|
|
738
|
-
await this.jobsRepo.updateLockTime(
|
|
817
|
+
const didUpdateLock = await this.jobsRepo.updateLockTime(
|
|
818
|
+
jobToRun.jobId,
|
|
819
|
+
effectiveLockTime,
|
|
820
|
+
jobToRun.lockId
|
|
821
|
+
);
|
|
822
|
+
if (!didUpdateLock) {
|
|
823
|
+
logger3.warn(`Will not execute job "${jobToRun.name}" because its lock is no longer owned`);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
739
826
|
}
|
|
740
827
|
const tracer = trace.getTracer("orionjs.dogs", "1.0");
|
|
741
828
|
await tracer.startActiveSpan(`job.${jobToRun.name}.${jobToRun.executionId}`, async (span) => {
|
|
742
829
|
try {
|
|
743
830
|
const startedAt = /* @__PURE__ */ new Date();
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
831
|
+
let executionStatus = "running";
|
|
832
|
+
let staleHandlingPromise;
|
|
833
|
+
let context;
|
|
834
|
+
const markAsStale = () => {
|
|
835
|
+
if (executionStatus === "stale") return staleHandlingPromise;
|
|
836
|
+
executionStatus = "stale";
|
|
837
|
+
context.clearStaleTimeout();
|
|
838
|
+
staleHandlingPromise = (async () => {
|
|
839
|
+
context.logger.warn(`Job "${jobToRun.name}" is stale`);
|
|
840
|
+
if (job.onStale) {
|
|
841
|
+
void Promise.resolve(job.onStale(jobToRun.params, context)).catch((error) => {
|
|
842
|
+
context.logger.error(`Error in onStale callback for job "${jobToRun.name}"`, {
|
|
843
|
+
error
|
|
844
|
+
});
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
try {
|
|
848
|
+
await this.jobsRepo.setJobRecordPriority(jobToRun.jobId, 0, jobToRun.lockId);
|
|
849
|
+
} catch (error) {
|
|
850
|
+
context.logger.error(`Error lowering priority for stale job "${jobToRun.name}"`, {
|
|
851
|
+
error
|
|
852
|
+
});
|
|
853
|
+
} finally {
|
|
854
|
+
onExecutionStale();
|
|
855
|
+
}
|
|
856
|
+
await this.saveExecution({
|
|
857
|
+
startedAt,
|
|
858
|
+
status: "stale",
|
|
859
|
+
result: null,
|
|
860
|
+
errorMessage: null,
|
|
861
|
+
job,
|
|
862
|
+
jobToRun
|
|
863
|
+
});
|
|
864
|
+
})().catch((error) => {
|
|
865
|
+
context.logger.error("Error handling stale execution", { error });
|
|
762
866
|
});
|
|
867
|
+
return staleHandlingPromise;
|
|
763
868
|
};
|
|
764
|
-
|
|
869
|
+
context = this.getContext(
|
|
870
|
+
job,
|
|
871
|
+
jobToRun,
|
|
872
|
+
() => void markAsStale(),
|
|
873
|
+
() => executionStatus === "running"
|
|
874
|
+
);
|
|
765
875
|
const extraContext = {
|
|
766
876
|
controllerType: "job",
|
|
767
877
|
jobName: jobToRun.name,
|
|
@@ -777,6 +887,18 @@ var Executor = class {
|
|
|
777
887
|
});
|
|
778
888
|
const result = await job.resolve(jobToRun.params, context);
|
|
779
889
|
context.clearStaleTimeout();
|
|
890
|
+
if (executionStatus === "stale") {
|
|
891
|
+
await staleHandlingPromise;
|
|
892
|
+
context.logger.warn(
|
|
893
|
+
`Ignoring late success from stale execution "${jobToRun.executionId}"`
|
|
894
|
+
);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
const stillOwnsJob = await this.afterExecutionSuccess(job, jobToRun, context);
|
|
898
|
+
if (!stillOwnsJob) {
|
|
899
|
+
await markAsStale();
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
780
902
|
void this.saveExecution({
|
|
781
903
|
startedAt,
|
|
782
904
|
status: "success",
|
|
@@ -787,9 +909,21 @@ var Executor = class {
|
|
|
787
909
|
}).catch((error) => {
|
|
788
910
|
context.logger.error("Error saving successful execution history", { error });
|
|
789
911
|
});
|
|
790
|
-
await this.afterExecutionSuccess(job, jobToRun, context);
|
|
791
912
|
} catch (error) {
|
|
792
913
|
context.clearStaleTimeout();
|
|
914
|
+
if (executionStatus === "stale") {
|
|
915
|
+
await staleHandlingPromise;
|
|
916
|
+
context.logger.warn(
|
|
917
|
+
`Ignoring late error from stale execution "${jobToRun.executionId}"`,
|
|
918
|
+
{ error }
|
|
919
|
+
);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
const stillOwnsJob = await this.onError(error, job, jobToRun, context);
|
|
923
|
+
if (!stillOwnsJob) {
|
|
924
|
+
await markAsStale();
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
793
927
|
void this.saveExecution({
|
|
794
928
|
startedAt,
|
|
795
929
|
status: "error",
|
|
@@ -800,7 +934,6 @@ var Executor = class {
|
|
|
800
934
|
}).catch((saveError) => {
|
|
801
935
|
context.logger.error("Error saving failed execution history", { error: saveError });
|
|
802
936
|
});
|
|
803
|
-
await this.onError(error, job, jobToRun, context);
|
|
804
937
|
}
|
|
805
938
|
});
|
|
806
939
|
} catch (error) {
|
|
@@ -826,6 +959,7 @@ var _executor_dec, _jobsRepo_dec3, _WorkerService_decorators, _init5;
|
|
|
826
959
|
_WorkerService_decorators = [Service3()], _jobsRepo_dec3 = [Inject3(() => JobsRepo)], _executor_dec = [Inject3(() => Executor)];
|
|
827
960
|
var WorkerService = class {
|
|
828
961
|
constructor() {
|
|
962
|
+
__publicField(this, "jobAcquisitionHintProbeIntervalMs", 30 * 60 * 1e3);
|
|
829
963
|
__publicField(this, "jobsRepo", __runInitializers(_init5, 8, this)), __runInitializers(_init5, 11, this);
|
|
830
964
|
__publicField(this, "executor", __runInitializers(_init5, 12, this)), __runInitializers(_init5, 15, this);
|
|
831
965
|
}
|
|
@@ -860,87 +994,242 @@ var WorkerService = class {
|
|
|
860
994
|
}
|
|
861
995
|
workersInstance.runningJobsByName.set(jobName, currentExecutions - 1);
|
|
862
996
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
workersInstance.
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
return await callback();
|
|
872
|
-
} finally {
|
|
873
|
-
releaseLock();
|
|
997
|
+
releaseExecutionCapacity(workersInstance, execution, nextStatus) {
|
|
998
|
+
if (execution.status !== "running") return;
|
|
999
|
+
execution.status = nextStatus;
|
|
1000
|
+
workersInstance.activeExecutions.delete(execution.job.executionId);
|
|
1001
|
+
this.releaseJobExecution(workersInstance, execution.job.name);
|
|
1002
|
+
execution.releaseCapacity();
|
|
1003
|
+
if (nextStatus === "stale") {
|
|
1004
|
+
workersInstance.staleExecutions.set(execution.job.executionId, execution);
|
|
874
1005
|
}
|
|
875
1006
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1007
|
+
startExecution(config, workersInstance, jobToRun) {
|
|
1008
|
+
let releaseCapacity;
|
|
1009
|
+
const capacityPromise = new Promise((resolve) => {
|
|
1010
|
+
releaseCapacity = resolve;
|
|
1011
|
+
});
|
|
1012
|
+
const execution = {
|
|
1013
|
+
job: jobToRun,
|
|
1014
|
+
promise: Promise.resolve(),
|
|
1015
|
+
capacityPromise,
|
|
1016
|
+
releaseCapacity,
|
|
1017
|
+
status: "running"
|
|
1018
|
+
};
|
|
1019
|
+
this.reserveJobExecution(workersInstance, jobToRun.name);
|
|
1020
|
+
workersInstance.activeExecutions.set(jobToRun.executionId, execution);
|
|
1021
|
+
logger4.debug(`Starting job execution "${jobToRun.executionId}"`, jobToRun);
|
|
1022
|
+
const executionPromise = this.executor.executeJob(config, jobToRun, () => {
|
|
1023
|
+
this.releaseExecutionCapacity(workersInstance, execution, "stale");
|
|
1024
|
+
});
|
|
1025
|
+
execution.promise = executionPromise;
|
|
1026
|
+
void executionPromise.catch((error) => {
|
|
1027
|
+
logger4.error(`Unhandled error executing job "${jobToRun.name}"`, { error });
|
|
1028
|
+
}).finally(() => {
|
|
1029
|
+
if (execution.status === "running") {
|
|
1030
|
+
this.releaseExecutionCapacity(workersInstance, execution, "finished");
|
|
1031
|
+
} else if (execution.status === "stale") {
|
|
1032
|
+
workersInstance.staleExecutions.delete(jobToRun.executionId);
|
|
1033
|
+
}
|
|
884
1034
|
});
|
|
885
1035
|
}
|
|
886
|
-
async
|
|
887
|
-
|
|
888
|
-
if (!jobToRun) {
|
|
889
|
-
logger4.debug("No job to run");
|
|
890
|
-
return false;
|
|
891
|
-
}
|
|
892
|
-
logger4.debug(`Got job [w${workerInstance.workerIndex}] to run:`, jobToRun);
|
|
893
|
-
try {
|
|
894
|
-
await this.executor.executeJob(executeConfig, jobToRun, workerInstance.respawn);
|
|
895
|
-
} finally {
|
|
896
|
-
this.releaseJobExecution(workersInstance, jobToRun.name);
|
|
897
|
-
}
|
|
898
|
-
return true;
|
|
1036
|
+
async waitForPollOrStop(config, workersInstance) {
|
|
1037
|
+
await Promise.race([sleep(config.pollInterval), workersInstance.stopSignal]);
|
|
899
1038
|
}
|
|
900
|
-
async
|
|
901
|
-
const
|
|
902
|
-
|
|
903
|
-
|
|
1039
|
+
async waitForCapacityOrStop(workersInstance) {
|
|
1040
|
+
const capacityPromises = Array.from(
|
|
1041
|
+
workersInstance.activeExecutions.values(),
|
|
1042
|
+
(execution) => execution.capacityPromise
|
|
904
1043
|
);
|
|
1044
|
+
if (capacityPromises.length === 0) return;
|
|
1045
|
+
await Promise.race([...capacityPromises, workersInstance.stopSignal]);
|
|
1046
|
+
}
|
|
1047
|
+
async runSchedulerLoop(config, workersInstance) {
|
|
1048
|
+
const jobNames = this.getJobNames(config.jobs);
|
|
905
1049
|
const executeConfig = {
|
|
906
1050
|
jobs: config.jobs,
|
|
907
1051
|
maxTries: config.maxTries,
|
|
908
1052
|
maxTriesReachedRetentionMs: config.maxTriesReachedRetentionMs,
|
|
909
1053
|
onMaxTriesReached: config.onMaxTriesReached
|
|
910
1054
|
};
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
}
|
|
1055
|
+
logger4.debug(`Running scheduler loop for jobs "${jobNames.join(", ")}"...`);
|
|
1056
|
+
while (workersInstance.running) {
|
|
1057
|
+
let didMiss = false;
|
|
1058
|
+
let allJobNamesAtCapacity = false;
|
|
916
1059
|
try {
|
|
917
|
-
|
|
918
|
-
config,
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1060
|
+
while (workersInstance.activeExecutions.size < config.workersCount) {
|
|
1061
|
+
const availableJobNames = this.getAvailableJobNames(config, workersInstance, jobNames);
|
|
1062
|
+
if (availableJobNames.length === 0) {
|
|
1063
|
+
allJobNamesAtCapacity = workersInstance.activeExecutions.size > 0;
|
|
1064
|
+
break;
|
|
1065
|
+
}
|
|
1066
|
+
const jobToRun = await this.jobsRepo.getJobAndLock(
|
|
1067
|
+
availableJobNames,
|
|
1068
|
+
config.defaultLockTime,
|
|
1069
|
+
workersInstance.jobAcquisitionHint
|
|
1070
|
+
);
|
|
1071
|
+
if (!jobToRun) {
|
|
1072
|
+
logger4.debug("No job to run");
|
|
1073
|
+
didMiss = true;
|
|
1074
|
+
break;
|
|
1075
|
+
}
|
|
1076
|
+
this.startExecution(executeConfig, workersInstance, jobToRun);
|
|
1077
|
+
if (!workersInstance.running) break;
|
|
1078
|
+
}
|
|
926
1079
|
} catch (error) {
|
|
927
|
-
logger4.error("Error in job
|
|
928
|
-
|
|
1080
|
+
logger4.error("Error in job scheduler.", { error });
|
|
1081
|
+
didMiss = true;
|
|
1082
|
+
}
|
|
1083
|
+
if (!workersInstance.running) return;
|
|
1084
|
+
const concurrencyIsFull = workersInstance.activeExecutions.size >= config.workersCount && workersInstance.activeExecutions.size > 0;
|
|
1085
|
+
if (concurrencyIsFull || allJobNamesAtCapacity) {
|
|
1086
|
+
await this.waitForCapacityOrStop(workersInstance);
|
|
1087
|
+
} else if (didMiss || workersInstance.activeExecutions.size === 0) {
|
|
1088
|
+
await this.waitForPollOrStop(config, workersInstance);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
getOtherJobAcquisitionHint(hint) {
|
|
1093
|
+
return hint === INITIAL_JOB_ACQUISITION_HINT ? CANDIDATE_JOB_ACQUISITION_HINT : INITIAL_JOB_ACQUISITION_HINT;
|
|
1094
|
+
}
|
|
1095
|
+
getMedian(values) {
|
|
1096
|
+
const sortedValues = [...values].sort((left, right) => left - right);
|
|
1097
|
+
return sortedValues[Math.floor(sortedValues.length / 2)];
|
|
1098
|
+
}
|
|
1099
|
+
resetPendingJobAcquisitionHint(workersInstance) {
|
|
1100
|
+
workersInstance.pendingJobAcquisitionHint = void 0;
|
|
1101
|
+
workersInstance.pendingJobAcquisitionHintWins = 0;
|
|
1102
|
+
}
|
|
1103
|
+
async runJobAcquisitionHintProbe(config, workersInstance) {
|
|
1104
|
+
const jobNames = this.getJobNames(config.jobs);
|
|
1105
|
+
if (jobNames.length === 0 || !workersInstance.running) return;
|
|
1106
|
+
const byHint = {
|
|
1107
|
+
[INITIAL_JOB_ACQUISITION_HINT]: [],
|
|
1108
|
+
[CANDIDATE_JOB_ACQUISITION_HINT]: []
|
|
1109
|
+
};
|
|
1110
|
+
const startsWithInitialHint = workersInstance.jobAcquisitionHintProbeCycle % 2 === 0;
|
|
1111
|
+
const firstHint = startsWithInitialHint ? INITIAL_JOB_ACQUISITION_HINT : CANDIDATE_JOB_ACQUISITION_HINT;
|
|
1112
|
+
const secondHint = this.getOtherJobAcquisitionHint(firstHint);
|
|
1113
|
+
workersInstance.jobAcquisitionHintProbeCycle++;
|
|
1114
|
+
try {
|
|
1115
|
+
for (let sample = 0; sample < 3; sample++) {
|
|
1116
|
+
for (const hint of [firstHint, secondHint]) {
|
|
1117
|
+
if (!workersInstance.running) return;
|
|
1118
|
+
const executionTimeMillis = await this.jobsRepo.getJobAcquisitionExecutionTime(
|
|
1119
|
+
jobNames,
|
|
1120
|
+
hint
|
|
1121
|
+
);
|
|
1122
|
+
if (!workersInstance.running) return;
|
|
1123
|
+
byHint[hint].push(executionTimeMillis);
|
|
1124
|
+
}
|
|
929
1125
|
}
|
|
1126
|
+
} catch (error) {
|
|
1127
|
+
this.resetPendingJobAcquisitionHint(workersInstance);
|
|
1128
|
+
if (workersInstance.running) {
|
|
1129
|
+
logger4.warn("Job acquisition hint probe failed; keeping the current hint.", { error });
|
|
1130
|
+
}
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const currentHint = workersInstance.jobAcquisitionHint;
|
|
1134
|
+
const otherHint = this.getOtherJobAcquisitionHint(currentHint);
|
|
1135
|
+
const currentMedian = this.getMedian(byHint[currentHint]);
|
|
1136
|
+
const otherMedian = this.getMedian(byHint[otherHint]);
|
|
1137
|
+
const winner = otherMedian < currentMedian ? otherHint : currentHint;
|
|
1138
|
+
if (!workersInstance.hasCompletedInitialJobAcquisitionHintProbe) {
|
|
1139
|
+
workersInstance.hasCompletedInitialJobAcquisitionHintProbe = true;
|
|
1140
|
+
workersInstance.jobAcquisitionHint = winner;
|
|
1141
|
+
this.resetPendingJobAcquisitionHint(workersInstance);
|
|
1142
|
+
logger4.info(`Selected job acquisition hint "${winner}" after startup probe.`);
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
if (winner === currentHint) {
|
|
1146
|
+
this.resetPendingJobAcquisitionHint(workersInstance);
|
|
1147
|
+
logger4.debug(`Keeping job acquisition hint "${currentHint}" after probe.`);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
if (workersInstance.pendingJobAcquisitionHint === winner) {
|
|
1151
|
+
workersInstance.pendingJobAcquisitionHintWins++;
|
|
1152
|
+
} else {
|
|
1153
|
+
workersInstance.pendingJobAcquisitionHint = winner;
|
|
1154
|
+
workersInstance.pendingJobAcquisitionHintWins = 1;
|
|
1155
|
+
}
|
|
1156
|
+
if (workersInstance.pendingJobAcquisitionHintWins < 2) {
|
|
1157
|
+
logger4.debug(
|
|
1158
|
+
`Keeping job acquisition hint "${currentHint}"; "${winner}" has one consecutive probe win.`
|
|
1159
|
+
);
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
workersInstance.jobAcquisitionHint = winner;
|
|
1163
|
+
this.resetPendingJobAcquisitionHint(workersInstance);
|
|
1164
|
+
logger4.info(`Changed job acquisition hint from "${currentHint}" to "${winner}".`);
|
|
1165
|
+
}
|
|
1166
|
+
async waitForNextJobAcquisitionHintProbe(workersInstance) {
|
|
1167
|
+
await new Promise((resolve) => {
|
|
1168
|
+
let resolved = false;
|
|
1169
|
+
const finishWait = () => {
|
|
1170
|
+
if (resolved) return;
|
|
1171
|
+
resolved = true;
|
|
1172
|
+
clearTimeout(workersInstance.jobAcquisitionHintProbeTimer);
|
|
1173
|
+
workersInstance.jobAcquisitionHintProbeTimer = void 0;
|
|
1174
|
+
workersInstance.cancelJobAcquisitionHintProbeWait = void 0;
|
|
1175
|
+
resolve();
|
|
1176
|
+
};
|
|
1177
|
+
workersInstance.jobAcquisitionHintProbeTimer = setTimeout(
|
|
1178
|
+
finishWait,
|
|
1179
|
+
this.jobAcquisitionHintProbeIntervalMs
|
|
1180
|
+
);
|
|
1181
|
+
workersInstance.cancelJobAcquisitionHintProbeWait = finishWait;
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
async runJobAcquisitionHintProbeLoop(config, workersInstance) {
|
|
1185
|
+
if (this.getJobNames(config.jobs).length === 0) return;
|
|
1186
|
+
while (workersInstance.running) {
|
|
1187
|
+
await this.runJobAcquisitionHintProbe(config, workersInstance);
|
|
1188
|
+
if (!workersInstance.running) return;
|
|
1189
|
+
await this.waitForNextJobAcquisitionHintProbe(workersInstance);
|
|
930
1190
|
}
|
|
931
1191
|
}
|
|
932
1192
|
createWorkersInstanceDefinition(config) {
|
|
1193
|
+
let signalStop;
|
|
1194
|
+
const stopSignal = new Promise((resolve) => {
|
|
1195
|
+
signalStop = resolve;
|
|
1196
|
+
});
|
|
933
1197
|
const workersInstance = {
|
|
934
1198
|
running: true,
|
|
935
1199
|
workersCount: config.workersCount,
|
|
936
|
-
|
|
1200
|
+
activeExecutions: /* @__PURE__ */ new Map(),
|
|
1201
|
+
staleExecutions: /* @__PURE__ */ new Map(),
|
|
937
1202
|
runningJobsByName: /* @__PURE__ */ new Map(),
|
|
938
|
-
|
|
1203
|
+
jobAcquisitionHint: INITIAL_JOB_ACQUISITION_HINT,
|
|
1204
|
+
jobAcquisitionHintProbeCycle: 0,
|
|
1205
|
+
hasCompletedInitialJobAcquisitionHintProbe: false,
|
|
1206
|
+
pendingJobAcquisitionHintWins: 0,
|
|
1207
|
+
jobAcquisitionHintProbePromise: Promise.resolve(),
|
|
1208
|
+
schedulerPromise: Promise.resolve(),
|
|
1209
|
+
stopSignal,
|
|
1210
|
+
signalStop,
|
|
1211
|
+
get runningExecutions() {
|
|
1212
|
+
return this.activeExecutions.size;
|
|
1213
|
+
},
|
|
939
1214
|
stop: async () => {
|
|
940
|
-
|
|
941
|
-
workersInstance.
|
|
942
|
-
|
|
943
|
-
|
|
1215
|
+
if (workersInstance.stopPromise) return workersInstance.stopPromise;
|
|
1216
|
+
workersInstance.stopPromise = (async () => {
|
|
1217
|
+
var _a;
|
|
1218
|
+
logger4.info("Stopping job scheduler...");
|
|
1219
|
+
workersInstance.running = false;
|
|
1220
|
+
workersInstance.signalStop();
|
|
1221
|
+
(_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
|
|
1222
|
+
await Promise.all([
|
|
1223
|
+
workersInstance.schedulerPromise,
|
|
1224
|
+
workersInstance.jobAcquisitionHintProbePromise
|
|
1225
|
+
]);
|
|
1226
|
+
const activeCapacity = Array.from(
|
|
1227
|
+
workersInstance.activeExecutions.values(),
|
|
1228
|
+
(execution) => execution.capacityPromise
|
|
1229
|
+
);
|
|
1230
|
+
await Promise.all(activeCapacity);
|
|
1231
|
+
})();
|
|
1232
|
+
return workersInstance.stopPromise;
|
|
944
1233
|
}
|
|
945
1234
|
};
|
|
946
1235
|
return workersInstance;
|
|
@@ -954,44 +1243,17 @@ var WorkerService = class {
|
|
|
954
1243
|
})
|
|
955
1244
|
);
|
|
956
1245
|
}
|
|
957
|
-
async
|
|
958
|
-
if (!workersInstance.running) {
|
|
959
|
-
return;
|
|
960
|
-
}
|
|
961
|
-
const workerInstance = {
|
|
962
|
-
running: true,
|
|
963
|
-
workerIndex,
|
|
964
|
-
stop: async () => {
|
|
965
|
-
logger4.info(`Stopping worker [w${workerIndex}]...`);
|
|
966
|
-
workerInstance.running = false;
|
|
967
|
-
await workerInstance.promise;
|
|
968
|
-
},
|
|
969
|
-
respawn: async () => {
|
|
970
|
-
logger4.info(`Respawning worker [w${workerIndex}]...`);
|
|
971
|
-
workerInstance.running = false;
|
|
972
|
-
await this.startANewWorker(config, workersInstance, workerIndex);
|
|
973
|
-
}
|
|
974
|
-
};
|
|
975
|
-
const workerPromise = this.startWorker(config, workersInstance, workerInstance);
|
|
976
|
-
workerInstance.promise = workerPromise;
|
|
977
|
-
workersInstance.workers[workerIndex] = workerInstance;
|
|
978
|
-
}
|
|
979
|
-
async runWorkers(config, workersInstance) {
|
|
1246
|
+
async runScheduler(config, workersInstance) {
|
|
980
1247
|
logger4.debug("Will ensure maxTriesReached retention and TTL index");
|
|
981
|
-
|
|
1248
|
+
void this.jobsRepo.ensureMaxTriesReachedRetention(config.maxTriesReachedRetentionMs).catch((error) => logger4.error("Error ensuring maxTriesReached retention", { error }));
|
|
982
1249
|
logger4.debug("Will ensure records for recurrent jobs");
|
|
983
1250
|
await this.ensureRecords(config);
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
logger4.info(`Starting ${workersCount} ${workerWord}`);
|
|
987
|
-
for (let workerIndex = 0; workerIndex < workersCount; workerIndex++) {
|
|
988
|
-
this.startANewWorker(config, workersInstance, workerIndex);
|
|
989
|
-
}
|
|
990
|
-
await ensureRetentionPromise;
|
|
1251
|
+
logger4.info(`Starting job scheduler with concurrency ${config.workersCount}`);
|
|
1252
|
+
await this.runSchedulerLoop(config, workersInstance);
|
|
991
1253
|
}
|
|
992
1254
|
/**
|
|
993
|
-
* Starts
|
|
994
|
-
* @param userConfig - Configuration for the
|
|
1255
|
+
* Starts one job scheduler with the provided concurrency configuration.
|
|
1256
|
+
* @param userConfig - Configuration for the scheduler. Required field: jobs
|
|
995
1257
|
*/
|
|
996
1258
|
startWorkers(userConfig) {
|
|
997
1259
|
const maxTriesReachedRetentionMs = userConfig.maxTriesReachedRetentionMs === void 0 ? DEFAULT_MAX_TRIES_REACHED_RETENTION_MS : userConfig.maxTriesReachedRetentionMs;
|
|
@@ -999,7 +1261,6 @@ var WorkerService = class {
|
|
|
999
1261
|
throw new Error("maxTriesReachedRetentionMs must be null, zero, or a positive number");
|
|
1000
1262
|
}
|
|
1001
1263
|
const config = {
|
|
1002
|
-
cooldownPeriod: 100,
|
|
1003
1264
|
pollInterval: 3e3,
|
|
1004
1265
|
workersCount: 4,
|
|
1005
1266
|
defaultLockTime: 30 * 1e3,
|
|
@@ -1008,8 +1269,18 @@ var WorkerService = class {
|
|
|
1008
1269
|
};
|
|
1009
1270
|
setNameToJobs(config.jobs);
|
|
1010
1271
|
const workersInstance = this.createWorkersInstanceDefinition(config);
|
|
1011
|
-
logger4.debug("Starting
|
|
1012
|
-
this.
|
|
1272
|
+
logger4.debug("Starting job scheduler", config);
|
|
1273
|
+
workersInstance.jobAcquisitionHintProbePromise = this.runJobAcquisitionHintProbeLoop(
|
|
1274
|
+
config,
|
|
1275
|
+
workersInstance
|
|
1276
|
+
);
|
|
1277
|
+
workersInstance.schedulerPromise = this.runScheduler(config, workersInstance).catch((error) => {
|
|
1278
|
+
var _a;
|
|
1279
|
+
workersInstance.running = false;
|
|
1280
|
+
workersInstance.signalStop();
|
|
1281
|
+
(_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
|
|
1282
|
+
logger4.error("Job scheduler stopped after an unexpected error.", { error });
|
|
1283
|
+
});
|
|
1013
1284
|
return workersInstance;
|
|
1014
1285
|
}
|
|
1015
1286
|
};
|