@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.cjs
CHANGED
|
@@ -115,6 +115,11 @@ var JobRecordSchema = (0, import_schema.schemaWithName)("JobRecord", {
|
|
|
115
115
|
nextRunAt: { type: "date" },
|
|
116
116
|
lastRunAt: { type: "date", optional: true },
|
|
117
117
|
lockedUntil: { type: "date", optional: true },
|
|
118
|
+
/**
|
|
119
|
+
* Identifies the execution that currently owns the lock. Mutations from an execution must match
|
|
120
|
+
* this value so a stale execution cannot modify a newer claim.
|
|
121
|
+
*/
|
|
122
|
+
lockId: { type: "string", optional: true },
|
|
118
123
|
tries: { type: "number", optional: true },
|
|
119
124
|
params: { type: "blackbox", optional: true },
|
|
120
125
|
/**
|
|
@@ -137,6 +142,35 @@ var JobRecordSchema = (0, import_schema.schemaWithName)("JobRecord", {
|
|
|
137
142
|
var DEFAULT_MAX_TRIES_REACHED_RETENTION_MS = 1e3 * 60 * 60 * 24 * 7;
|
|
138
143
|
|
|
139
144
|
// src/repos/JobsRepo.ts
|
|
145
|
+
var JOB_ACQUISITION_HINTS = {
|
|
146
|
+
byJobName: {
|
|
147
|
+
jobName: 1,
|
|
148
|
+
priority: -1,
|
|
149
|
+
nextRunAt: 1
|
|
150
|
+
},
|
|
151
|
+
globalPriority: {
|
|
152
|
+
priority: -1,
|
|
153
|
+
nextRunAt: 1
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
var INITIAL_JOB_ACQUISITION_HINT = "byJobName";
|
|
157
|
+
var CANDIDATE_JOB_ACQUISITION_HINT = "globalPriority";
|
|
158
|
+
var JOB_ACQUISITION_SORT = {
|
|
159
|
+
priority: -1,
|
|
160
|
+
nextRunAt: 1
|
|
161
|
+
};
|
|
162
|
+
function getJobAcquisitionSelector(jobNames, now) {
|
|
163
|
+
return {
|
|
164
|
+
jobName: { $in: jobNames },
|
|
165
|
+
nextRunAt: { $lte: now },
|
|
166
|
+
$and: [
|
|
167
|
+
{ $or: [{ lockedUntil: { $exists: false } }, { lockedUntil: { $lte: now } }] },
|
|
168
|
+
// Exclude event jobs that have reached max tries. Recurrent jobs keep running even
|
|
169
|
+
// if old records still have this status from previous versions.
|
|
170
|
+
{ $or: [{ type: { $ne: "event" } }, { status: { $ne: "maxTriesReached" } }] }
|
|
171
|
+
]
|
|
172
|
+
};
|
|
173
|
+
}
|
|
140
174
|
var _jobs_dec, _JobsRepo_decorators, _init;
|
|
141
175
|
_JobsRepo_decorators = [(0, import_mongodb.Repository)()], _jobs_dec = [(0, import_mongodb.MongoCollection)({
|
|
142
176
|
idGeneration: "uuid",
|
|
@@ -144,11 +178,10 @@ _JobsRepo_decorators = [(0, import_mongodb.Repository)()], _jobs_dec = [(0, impo
|
|
|
144
178
|
schema: JobRecordSchema,
|
|
145
179
|
indexes: [
|
|
146
180
|
{
|
|
147
|
-
keys:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
181
|
+
keys: JOB_ACQUISITION_HINTS.byJobName
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
keys: JOB_ACQUISITION_HINTS.globalPriority
|
|
152
185
|
},
|
|
153
186
|
{
|
|
154
187
|
keys: {
|
|
@@ -181,29 +214,20 @@ var JobsRepo = class {
|
|
|
181
214
|
constructor() {
|
|
182
215
|
__publicField(this, "jobs", __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
|
|
183
216
|
}
|
|
184
|
-
async getJobAndLock(jobNames, lockTime) {
|
|
185
|
-
const
|
|
217
|
+
async getJobAndLock(jobNames, lockTime, hint = INITIAL_JOB_ACQUISITION_HINT) {
|
|
218
|
+
const executionId = (0, import_helpers.generateId)();
|
|
219
|
+
const now = /* @__PURE__ */ new Date();
|
|
220
|
+
const lockedUntil = new Date(now.getTime() + lockTime);
|
|
186
221
|
const job = await this.jobs.findOneAndUpdate(
|
|
222
|
+
getJobAcquisitionSelector(jobNames, now),
|
|
187
223
|
{
|
|
188
|
-
|
|
189
|
-
nextRunAt: { $lte: /* @__PURE__ */ new Date() },
|
|
190
|
-
$and: [
|
|
191
|
-
{ $or: [{ lockedUntil: { $exists: false } }, { lockedUntil: { $lte: /* @__PURE__ */ new Date() } }] },
|
|
192
|
-
// Exclude event jobs that have reached max tries. Recurrent jobs keep running even
|
|
193
|
-
// if old records still have this status from previous versions.
|
|
194
|
-
{ $or: [{ type: { $ne: "event" } }, { status: { $ne: "maxTriesReached" } }] }
|
|
195
|
-
]
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
$set: { lockedUntil, lastRunAt: /* @__PURE__ */ new Date() },
|
|
224
|
+
$set: { lockId: executionId, lockedUntil, lastRunAt: now },
|
|
199
225
|
$inc: { tries: 1 }
|
|
200
226
|
},
|
|
201
227
|
{
|
|
202
228
|
mongoOptions: {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
nextRunAt: 1
|
|
206
|
-
},
|
|
229
|
+
hint: JOB_ACQUISITION_HINTS[hint],
|
|
230
|
+
sort: JOB_ACQUISITION_SORT,
|
|
207
231
|
returnDocument: "before"
|
|
208
232
|
}
|
|
209
233
|
}
|
|
@@ -216,7 +240,8 @@ var JobsRepo = class {
|
|
|
216
240
|
}
|
|
217
241
|
return {
|
|
218
242
|
jobId: job._id,
|
|
219
|
-
executionId
|
|
243
|
+
executionId,
|
|
244
|
+
lockId: executionId,
|
|
220
245
|
name: job.jobName,
|
|
221
246
|
params: job.params,
|
|
222
247
|
type: job.type,
|
|
@@ -227,8 +252,24 @@ var JobsRepo = class {
|
|
|
227
252
|
wasStale
|
|
228
253
|
};
|
|
229
254
|
}
|
|
230
|
-
async
|
|
231
|
-
|
|
255
|
+
async getJobAcquisitionExecutionTime(jobNames, hint) {
|
|
256
|
+
var _a;
|
|
257
|
+
const explain = await this.jobs.find(getJobAcquisitionSelector(jobNames, /* @__PURE__ */ new Date()), {
|
|
258
|
+
hint: JOB_ACQUISITION_HINTS[hint],
|
|
259
|
+
maxTimeMS: 1e3,
|
|
260
|
+
readPreference: "primary"
|
|
261
|
+
}).sort(JOB_ACQUISITION_SORT).limit(1).explain("executionStats");
|
|
262
|
+
const executionTimeMillis = (_a = explain.executionStats) == null ? void 0 : _a.executionTimeMillis;
|
|
263
|
+
if (typeof executionTimeMillis !== "number" || !Number.isFinite(executionTimeMillis)) {
|
|
264
|
+
throw new Error(`Explain for acquisition hint "${hint}" returned no execution time`);
|
|
265
|
+
}
|
|
266
|
+
return executionTimeMillis;
|
|
267
|
+
}
|
|
268
|
+
async setJobRecordPriority(jobId, priority, lockId) {
|
|
269
|
+
const result = await this.jobs.updateOne(lockId ? { _id: jobId, lockId } : jobId, {
|
|
270
|
+
$set: { priority }
|
|
271
|
+
});
|
|
272
|
+
return result.matchedCount > 0;
|
|
232
273
|
}
|
|
233
274
|
async scheduleNextRun(options) {
|
|
234
275
|
const updator = {
|
|
@@ -237,22 +278,31 @@ var JobsRepo = class {
|
|
|
237
278
|
priority: options.priority,
|
|
238
279
|
...options.resetTries ? { tries: 0 } : {}
|
|
239
280
|
},
|
|
240
|
-
$unset: { lockedUntil: "" }
|
|
281
|
+
$unset: { lockId: "", lockedUntil: "" }
|
|
241
282
|
};
|
|
242
|
-
await this.jobs.updateOne(
|
|
283
|
+
const result = await this.jobs.updateOne(
|
|
284
|
+
options.lockId ? { _id: options.jobId, lockId: options.lockId } : options.jobId,
|
|
285
|
+
updator
|
|
286
|
+
);
|
|
287
|
+
return result.matchedCount > 0;
|
|
243
288
|
}
|
|
244
|
-
async deleteEventJob(jobId) {
|
|
245
|
-
await this.jobs.deleteOne({
|
|
289
|
+
async deleteEventJob(jobId, lockId) {
|
|
290
|
+
const result = await this.jobs.deleteOne({
|
|
291
|
+
_id: jobId,
|
|
292
|
+
type: "event",
|
|
293
|
+
...lockId ? { lockId } : {}
|
|
294
|
+
});
|
|
295
|
+
return result.deletedCount > 0;
|
|
246
296
|
}
|
|
247
297
|
/**
|
|
248
298
|
* Marks an event job as having reached its maximum tries limit.
|
|
249
299
|
* The job won't be picked up for execution and MongoDB will delete it after
|
|
250
300
|
* the configured retention period.
|
|
251
301
|
*/
|
|
252
|
-
async markJobAsMaxTriesReached(jobId, retentionMs = DEFAULT_MAX_TRIES_REACHED_RETENTION_MS) {
|
|
302
|
+
async markJobAsMaxTriesReached(jobId, retentionMs = DEFAULT_MAX_TRIES_REACHED_RETENTION_MS, lockId) {
|
|
253
303
|
const maxTriesReachedAt = /* @__PURE__ */ new Date();
|
|
254
|
-
await this.jobs.updateOne(
|
|
255
|
-
{ _id: jobId, type: "event" },
|
|
304
|
+
const result = await this.jobs.updateOne(
|
|
305
|
+
{ _id: jobId, type: "event", ...lockId ? { lockId } : {} },
|
|
256
306
|
{
|
|
257
307
|
$set: {
|
|
258
308
|
status: "maxTriesReached",
|
|
@@ -260,11 +310,13 @@ var JobsRepo = class {
|
|
|
260
310
|
...retentionMs === null ? {} : { expiresAt: new Date(maxTriesReachedAt.getTime() + retentionMs) }
|
|
261
311
|
},
|
|
262
312
|
$unset: {
|
|
313
|
+
lockId: "",
|
|
263
314
|
lockedUntil: "",
|
|
264
315
|
...retentionMs === null ? { expiresAt: "" } : {}
|
|
265
316
|
}
|
|
266
317
|
}
|
|
267
318
|
);
|
|
319
|
+
return result.matchedCount > 0;
|
|
268
320
|
}
|
|
269
321
|
/**
|
|
270
322
|
* Applies the current retention configuration to existing maxTriesReached
|
|
@@ -293,23 +345,22 @@ var JobsRepo = class {
|
|
|
293
345
|
}
|
|
294
346
|
]);
|
|
295
347
|
}
|
|
296
|
-
async extendLockTime(jobId, extraTime) {
|
|
297
|
-
|
|
348
|
+
async extendLockTime(jobId, extraTime, lockId) {
|
|
349
|
+
return this.updateLockTime(jobId, extraTime, lockId);
|
|
298
350
|
}
|
|
299
351
|
/**
|
|
300
352
|
* Updates the lock time for a job to the specified duration from now.
|
|
301
353
|
* Can be used to both extend or shorten the lock time.
|
|
302
354
|
*/
|
|
303
|
-
async updateLockTime(jobId, lockDuration) {
|
|
355
|
+
async updateLockTime(jobId, lockDuration, lockId) {
|
|
304
356
|
const lockedUntil = new Date(Date.now() + lockDuration);
|
|
305
|
-
await this.jobs.updateOne(
|
|
306
|
-
{
|
|
307
|
-
_id: jobId
|
|
308
|
-
},
|
|
357
|
+
const result = await this.jobs.updateOne(
|
|
358
|
+
{ _id: jobId, ...lockId ? { lockId } : {} },
|
|
309
359
|
{
|
|
310
360
|
$set: { lockedUntil }
|
|
311
361
|
}
|
|
312
362
|
);
|
|
363
|
+
return result.matchedCount > 0;
|
|
313
364
|
}
|
|
314
365
|
async unlockAllJobs() {
|
|
315
366
|
const result = await this.jobs.updateMany(
|
|
@@ -317,7 +368,7 @@ var JobsRepo = class {
|
|
|
317
368
|
lockedUntil: { $exists: true }
|
|
318
369
|
},
|
|
319
370
|
{
|
|
320
|
-
$unset: { lockedUntil: "" }
|
|
371
|
+
$unset: { lockId: "", lockedUntil: "" }
|
|
321
372
|
}
|
|
322
373
|
);
|
|
323
374
|
return result.modifiedCount;
|
|
@@ -623,11 +674,15 @@ var Executor = class {
|
|
|
623
674
|
getEffectiveLockTime(job, jobToRun) {
|
|
624
675
|
return job.lockTime ?? jobToRun.lockTime;
|
|
625
676
|
}
|
|
626
|
-
getContext(job, jobToRun, onStale) {
|
|
677
|
+
getContext(job, jobToRun, onStale, isExecutionActive) {
|
|
627
678
|
var _a;
|
|
628
679
|
const effectiveLockTime = this.getEffectiveLockTime(job, jobToRun);
|
|
629
680
|
let staleTimeout = setTimeout(() => onStale(), effectiveLockTime);
|
|
630
681
|
(_a = staleTimeout.unref) == null ? void 0 : _a.call(staleTimeout);
|
|
682
|
+
const contextLogger = import_logger3.logger.addMetadata({
|
|
683
|
+
jobName: jobToRun.name,
|
|
684
|
+
jobId: jobToRun.jobId
|
|
685
|
+
});
|
|
631
686
|
return {
|
|
632
687
|
definition: job,
|
|
633
688
|
record: jobToRun,
|
|
@@ -635,15 +690,26 @@ var Executor = class {
|
|
|
635
690
|
clearStaleTimeout: () => clearTimeout(staleTimeout),
|
|
636
691
|
extendLockTime: async (extraTime) => {
|
|
637
692
|
var _a2;
|
|
693
|
+
if (!isExecutionActive()) {
|
|
694
|
+
contextLogger.warn(`Will not extend lock for stale execution "${jobToRun.executionId}"`);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
638
697
|
clearTimeout(staleTimeout);
|
|
698
|
+
let didExtend = false;
|
|
699
|
+
try {
|
|
700
|
+
didExtend = await this.jobsRepo.extendLockTime(jobToRun.jobId, extraTime, jobToRun.lockId);
|
|
701
|
+
} catch (error) {
|
|
702
|
+
onStale();
|
|
703
|
+
throw error;
|
|
704
|
+
}
|
|
705
|
+
if (!didExtend) {
|
|
706
|
+
onStale();
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
639
709
|
staleTimeout = setTimeout(() => onStale(), extraTime);
|
|
640
710
|
(_a2 = staleTimeout.unref) == null ? void 0 : _a2.call(staleTimeout);
|
|
641
|
-
await this.jobsRepo.extendLockTime(jobToRun.jobId, extraTime);
|
|
642
711
|
},
|
|
643
|
-
logger:
|
|
644
|
-
jobName: jobToRun.name,
|
|
645
|
-
jobId: jobToRun.jobId
|
|
646
|
-
})
|
|
712
|
+
logger: contextLogger
|
|
647
713
|
};
|
|
648
714
|
}
|
|
649
715
|
getJobDefinition(jobToRun, jobs) {
|
|
@@ -675,8 +741,16 @@ var Executor = class {
|
|
|
675
741
|
jobLogger.warn(
|
|
676
742
|
`Job "${jobToRun.name}" has exceeded max tries (${jobToRun.tries}). Marking as maxTriesReached.`
|
|
677
743
|
);
|
|
678
|
-
await this.jobsRepo.markJobAsMaxTriesReached(
|
|
679
|
-
|
|
744
|
+
const didMark = await this.jobsRepo.markJobAsMaxTriesReached(
|
|
745
|
+
jobToRun.jobId,
|
|
746
|
+
retentionMs,
|
|
747
|
+
jobToRun.lockId
|
|
748
|
+
);
|
|
749
|
+
if (!didMark) {
|
|
750
|
+
jobLogger.warn(`Could not mark job "${jobToRun.name}" because its lock is no longer owned`);
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
if (!onMaxTriesReached) return true;
|
|
680
754
|
try {
|
|
681
755
|
await onMaxTriesReached(jobToRun);
|
|
682
756
|
} catch (callbackError) {
|
|
@@ -684,30 +758,33 @@ var Executor = class {
|
|
|
684
758
|
error: callbackError
|
|
685
759
|
});
|
|
686
760
|
}
|
|
761
|
+
return true;
|
|
687
762
|
}
|
|
688
763
|
async onError(error, job, jobToRun, context) {
|
|
689
764
|
const scheduleRecurrent = async () => {
|
|
690
765
|
if (job.type === "recurrent") {
|
|
691
|
-
|
|
766
|
+
return this.jobsRepo.scheduleNextRun({
|
|
692
767
|
jobId: jobToRun.jobId,
|
|
693
768
|
nextRunAt: getNextRunDate(job),
|
|
694
769
|
resetTries: true,
|
|
695
|
-
priority: job.priority
|
|
770
|
+
priority: job.priority,
|
|
771
|
+
lockId: jobToRun.lockId
|
|
696
772
|
});
|
|
697
773
|
}
|
|
774
|
+
return true;
|
|
698
775
|
};
|
|
699
776
|
const handleRetry = async (nextRunAt) => {
|
|
700
|
-
|
|
777
|
+
return this.jobsRepo.scheduleNextRun({
|
|
701
778
|
jobId: jobToRun.jobId,
|
|
702
779
|
nextRunAt,
|
|
703
780
|
resetTries: false,
|
|
704
|
-
priority: job.type === "recurrent" ? job.priority : jobToRun.priority
|
|
781
|
+
priority: job.type === "recurrent" ? job.priority : jobToRun.priority,
|
|
782
|
+
lockId: jobToRun.lockId
|
|
705
783
|
});
|
|
706
784
|
};
|
|
707
785
|
if (!job.onError) {
|
|
708
786
|
context.logger.error(`Error executing job "${jobToRun.name}"`, { error });
|
|
709
|
-
|
|
710
|
-
return;
|
|
787
|
+
return scheduleRecurrent();
|
|
711
788
|
}
|
|
712
789
|
context.logger.info(`Error executing job "${jobToRun.name}"`, { error });
|
|
713
790
|
const result = await job.onError(
|
|
@@ -716,12 +793,12 @@ var Executor = class {
|
|
|
716
793
|
context
|
|
717
794
|
);
|
|
718
795
|
if (result.action === "dismiss") {
|
|
719
|
-
|
|
720
|
-
return;
|
|
796
|
+
return scheduleRecurrent();
|
|
721
797
|
}
|
|
722
798
|
if (result.action === "retry") {
|
|
723
|
-
|
|
799
|
+
return handleRetry(getNextRunDate(result));
|
|
724
800
|
}
|
|
801
|
+
return true;
|
|
725
802
|
}
|
|
726
803
|
async saveExecution(options) {
|
|
727
804
|
const { startedAt, status, errorMessage, result, job, jobToRun } = options;
|
|
@@ -751,19 +828,21 @@ var Executor = class {
|
|
|
751
828
|
async afterExecutionSuccess(job, jobToRun, context) {
|
|
752
829
|
if (job.type === "recurrent") {
|
|
753
830
|
context.logger.debug(`Scheduling next run for recurrent job "${jobToRun.name}"`);
|
|
754
|
-
|
|
831
|
+
return this.jobsRepo.scheduleNextRun({
|
|
755
832
|
jobId: jobToRun.jobId,
|
|
756
833
|
nextRunAt: getNextRunDate(job),
|
|
757
834
|
resetTries: true,
|
|
758
|
-
priority: job.priority
|
|
835
|
+
priority: job.priority,
|
|
836
|
+
lockId: jobToRun.lockId
|
|
759
837
|
});
|
|
760
838
|
}
|
|
761
839
|
if (job.type === "event") {
|
|
762
840
|
context.logger.debug(`Removing event job after success "${jobToRun.name}"`);
|
|
763
|
-
|
|
841
|
+
return this.jobsRepo.deleteEventJob(jobToRun.jobId, jobToRun.lockId);
|
|
764
842
|
}
|
|
843
|
+
return false;
|
|
765
844
|
}
|
|
766
|
-
async executeJob(config, jobToRun,
|
|
845
|
+
async executeJob(config, jobToRun, onExecutionStale) {
|
|
767
846
|
const job = this.getJobDefinition(jobToRun, config.jobs);
|
|
768
847
|
if (!job) return;
|
|
769
848
|
if (job.type === "event") {
|
|
@@ -779,33 +858,64 @@ var Executor = class {
|
|
|
779
858
|
}
|
|
780
859
|
const effectiveLockTime = this.getEffectiveLockTime(job, jobToRun);
|
|
781
860
|
if (effectiveLockTime !== jobToRun.lockTime) {
|
|
782
|
-
await this.jobsRepo.updateLockTime(
|
|
861
|
+
const didUpdateLock = await this.jobsRepo.updateLockTime(
|
|
862
|
+
jobToRun.jobId,
|
|
863
|
+
effectiveLockTime,
|
|
864
|
+
jobToRun.lockId
|
|
865
|
+
);
|
|
866
|
+
if (!didUpdateLock) {
|
|
867
|
+
import_logger3.logger.warn(`Will not execute job "${jobToRun.name}" because its lock is no longer owned`);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
783
870
|
}
|
|
784
871
|
const tracer = import_api.trace.getTracer("orionjs.dogs", "1.0");
|
|
785
872
|
await tracer.startActiveSpan(`job.${jobToRun.name}.${jobToRun.executionId}`, async (span) => {
|
|
786
873
|
try {
|
|
787
874
|
const startedAt = /* @__PURE__ */ new Date();
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
875
|
+
let executionStatus = "running";
|
|
876
|
+
let staleHandlingPromise;
|
|
877
|
+
let context;
|
|
878
|
+
const markAsStale = () => {
|
|
879
|
+
if (executionStatus === "stale") return staleHandlingPromise;
|
|
880
|
+
executionStatus = "stale";
|
|
881
|
+
context.clearStaleTimeout();
|
|
882
|
+
staleHandlingPromise = (async () => {
|
|
883
|
+
context.logger.warn(`Job "${jobToRun.name}" is stale`);
|
|
884
|
+
if (job.onStale) {
|
|
885
|
+
void Promise.resolve(job.onStale(jobToRun.params, context)).catch((error) => {
|
|
886
|
+
context.logger.error(`Error in onStale callback for job "${jobToRun.name}"`, {
|
|
887
|
+
error
|
|
888
|
+
});
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
try {
|
|
892
|
+
await this.jobsRepo.setJobRecordPriority(jobToRun.jobId, 0, jobToRun.lockId);
|
|
893
|
+
} catch (error) {
|
|
894
|
+
context.logger.error(`Error lowering priority for stale job "${jobToRun.name}"`, {
|
|
895
|
+
error
|
|
896
|
+
});
|
|
897
|
+
} finally {
|
|
898
|
+
onExecutionStale();
|
|
899
|
+
}
|
|
900
|
+
await this.saveExecution({
|
|
901
|
+
startedAt,
|
|
902
|
+
status: "stale",
|
|
903
|
+
result: null,
|
|
904
|
+
errorMessage: null,
|
|
905
|
+
job,
|
|
906
|
+
jobToRun
|
|
907
|
+
});
|
|
908
|
+
})().catch((error) => {
|
|
909
|
+
context.logger.error("Error handling stale execution", { error });
|
|
806
910
|
});
|
|
911
|
+
return staleHandlingPromise;
|
|
807
912
|
};
|
|
808
|
-
|
|
913
|
+
context = this.getContext(
|
|
914
|
+
job,
|
|
915
|
+
jobToRun,
|
|
916
|
+
() => void markAsStale(),
|
|
917
|
+
() => executionStatus === "running"
|
|
918
|
+
);
|
|
809
919
|
const extraContext = {
|
|
810
920
|
controllerType: "job",
|
|
811
921
|
jobName: jobToRun.name,
|
|
@@ -821,6 +931,18 @@ var Executor = class {
|
|
|
821
931
|
});
|
|
822
932
|
const result = await job.resolve(jobToRun.params, context);
|
|
823
933
|
context.clearStaleTimeout();
|
|
934
|
+
if (executionStatus === "stale") {
|
|
935
|
+
await staleHandlingPromise;
|
|
936
|
+
context.logger.warn(
|
|
937
|
+
`Ignoring late success from stale execution "${jobToRun.executionId}"`
|
|
938
|
+
);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
const stillOwnsJob = await this.afterExecutionSuccess(job, jobToRun, context);
|
|
942
|
+
if (!stillOwnsJob) {
|
|
943
|
+
await markAsStale();
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
824
946
|
void this.saveExecution({
|
|
825
947
|
startedAt,
|
|
826
948
|
status: "success",
|
|
@@ -831,9 +953,21 @@ var Executor = class {
|
|
|
831
953
|
}).catch((error) => {
|
|
832
954
|
context.logger.error("Error saving successful execution history", { error });
|
|
833
955
|
});
|
|
834
|
-
await this.afterExecutionSuccess(job, jobToRun, context);
|
|
835
956
|
} catch (error) {
|
|
836
957
|
context.clearStaleTimeout();
|
|
958
|
+
if (executionStatus === "stale") {
|
|
959
|
+
await staleHandlingPromise;
|
|
960
|
+
context.logger.warn(
|
|
961
|
+
`Ignoring late error from stale execution "${jobToRun.executionId}"`,
|
|
962
|
+
{ error }
|
|
963
|
+
);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
const stillOwnsJob = await this.onError(error, job, jobToRun, context);
|
|
967
|
+
if (!stillOwnsJob) {
|
|
968
|
+
await markAsStale();
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
837
971
|
void this.saveExecution({
|
|
838
972
|
startedAt,
|
|
839
973
|
status: "error",
|
|
@@ -844,7 +978,6 @@ var Executor = class {
|
|
|
844
978
|
}).catch((saveError) => {
|
|
845
979
|
context.logger.error("Error saving failed execution history", { error: saveError });
|
|
846
980
|
});
|
|
847
|
-
await this.onError(error, job, jobToRun, context);
|
|
848
981
|
}
|
|
849
982
|
});
|
|
850
983
|
} catch (error) {
|
|
@@ -870,6 +1003,7 @@ var _executor_dec, _jobsRepo_dec3, _WorkerService_decorators, _init5;
|
|
|
870
1003
|
_WorkerService_decorators = [(0, import_services3.Service)()], _jobsRepo_dec3 = [(0, import_services3.Inject)(() => JobsRepo)], _executor_dec = [(0, import_services3.Inject)(() => Executor)];
|
|
871
1004
|
var WorkerService = class {
|
|
872
1005
|
constructor() {
|
|
1006
|
+
__publicField(this, "jobAcquisitionHintProbeIntervalMs", 30 * 60 * 1e3);
|
|
873
1007
|
__publicField(this, "jobsRepo", __runInitializers(_init5, 8, this)), __runInitializers(_init5, 11, this);
|
|
874
1008
|
__publicField(this, "executor", __runInitializers(_init5, 12, this)), __runInitializers(_init5, 15, this);
|
|
875
1009
|
}
|
|
@@ -904,87 +1038,242 @@ var WorkerService = class {
|
|
|
904
1038
|
}
|
|
905
1039
|
workersInstance.runningJobsByName.set(jobName, currentExecutions - 1);
|
|
906
1040
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
workersInstance.
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
return await callback();
|
|
916
|
-
} finally {
|
|
917
|
-
releaseLock();
|
|
1041
|
+
releaseExecutionCapacity(workersInstance, execution, nextStatus) {
|
|
1042
|
+
if (execution.status !== "running") return;
|
|
1043
|
+
execution.status = nextStatus;
|
|
1044
|
+
workersInstance.activeExecutions.delete(execution.job.executionId);
|
|
1045
|
+
this.releaseJobExecution(workersInstance, execution.job.name);
|
|
1046
|
+
execution.releaseCapacity();
|
|
1047
|
+
if (nextStatus === "stale") {
|
|
1048
|
+
workersInstance.staleExecutions.set(execution.job.executionId, execution);
|
|
918
1049
|
}
|
|
919
1050
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1051
|
+
startExecution(config, workersInstance, jobToRun) {
|
|
1052
|
+
let releaseCapacity;
|
|
1053
|
+
const capacityPromise = new Promise((resolve) => {
|
|
1054
|
+
releaseCapacity = resolve;
|
|
1055
|
+
});
|
|
1056
|
+
const execution = {
|
|
1057
|
+
job: jobToRun,
|
|
1058
|
+
promise: Promise.resolve(),
|
|
1059
|
+
capacityPromise,
|
|
1060
|
+
releaseCapacity,
|
|
1061
|
+
status: "running"
|
|
1062
|
+
};
|
|
1063
|
+
this.reserveJobExecution(workersInstance, jobToRun.name);
|
|
1064
|
+
workersInstance.activeExecutions.set(jobToRun.executionId, execution);
|
|
1065
|
+
import_logger4.logger.debug(`Starting job execution "${jobToRun.executionId}"`, jobToRun);
|
|
1066
|
+
const executionPromise = this.executor.executeJob(config, jobToRun, () => {
|
|
1067
|
+
this.releaseExecutionCapacity(workersInstance, execution, "stale");
|
|
1068
|
+
});
|
|
1069
|
+
execution.promise = executionPromise;
|
|
1070
|
+
void executionPromise.catch((error) => {
|
|
1071
|
+
import_logger4.logger.error(`Unhandled error executing job "${jobToRun.name}"`, { error });
|
|
1072
|
+
}).finally(() => {
|
|
1073
|
+
if (execution.status === "running") {
|
|
1074
|
+
this.releaseExecutionCapacity(workersInstance, execution, "finished");
|
|
1075
|
+
} else if (execution.status === "stale") {
|
|
1076
|
+
workersInstance.staleExecutions.delete(jobToRun.executionId);
|
|
1077
|
+
}
|
|
928
1078
|
});
|
|
929
1079
|
}
|
|
930
|
-
async
|
|
931
|
-
|
|
932
|
-
if (!jobToRun) {
|
|
933
|
-
import_logger4.logger.debug("No job to run");
|
|
934
|
-
return false;
|
|
935
|
-
}
|
|
936
|
-
import_logger4.logger.debug(`Got job [w${workerInstance.workerIndex}] to run:`, jobToRun);
|
|
937
|
-
try {
|
|
938
|
-
await this.executor.executeJob(executeConfig, jobToRun, workerInstance.respawn);
|
|
939
|
-
} finally {
|
|
940
|
-
this.releaseJobExecution(workersInstance, jobToRun.name);
|
|
941
|
-
}
|
|
942
|
-
return true;
|
|
1080
|
+
async waitForPollOrStop(config, workersInstance) {
|
|
1081
|
+
await Promise.race([(0, import_helpers2.sleep)(config.pollInterval), workersInstance.stopSignal]);
|
|
943
1082
|
}
|
|
944
|
-
async
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
1083
|
+
async waitForCapacityOrStop(workersInstance) {
|
|
1084
|
+
const capacityPromises = Array.from(
|
|
1085
|
+
workersInstance.activeExecutions.values(),
|
|
1086
|
+
(execution) => execution.capacityPromise
|
|
948
1087
|
);
|
|
1088
|
+
if (capacityPromises.length === 0) return;
|
|
1089
|
+
await Promise.race([...capacityPromises, workersInstance.stopSignal]);
|
|
1090
|
+
}
|
|
1091
|
+
async runSchedulerLoop(config, workersInstance) {
|
|
1092
|
+
const jobNames = this.getJobNames(config.jobs);
|
|
949
1093
|
const executeConfig = {
|
|
950
1094
|
jobs: config.jobs,
|
|
951
1095
|
maxTries: config.maxTries,
|
|
952
1096
|
maxTriesReachedRetentionMs: config.maxTriesReachedRetentionMs,
|
|
953
1097
|
onMaxTriesReached: config.onMaxTriesReached
|
|
954
1098
|
};
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
}
|
|
1099
|
+
import_logger4.logger.debug(`Running scheduler loop for jobs "${jobNames.join(", ")}"...`);
|
|
1100
|
+
while (workersInstance.running) {
|
|
1101
|
+
let didMiss = false;
|
|
1102
|
+
let allJobNamesAtCapacity = false;
|
|
960
1103
|
try {
|
|
961
|
-
|
|
962
|
-
config,
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1104
|
+
while (workersInstance.activeExecutions.size < config.workersCount) {
|
|
1105
|
+
const availableJobNames = this.getAvailableJobNames(config, workersInstance, jobNames);
|
|
1106
|
+
if (availableJobNames.length === 0) {
|
|
1107
|
+
allJobNamesAtCapacity = workersInstance.activeExecutions.size > 0;
|
|
1108
|
+
break;
|
|
1109
|
+
}
|
|
1110
|
+
const jobToRun = await this.jobsRepo.getJobAndLock(
|
|
1111
|
+
availableJobNames,
|
|
1112
|
+
config.defaultLockTime,
|
|
1113
|
+
workersInstance.jobAcquisitionHint
|
|
1114
|
+
);
|
|
1115
|
+
if (!jobToRun) {
|
|
1116
|
+
import_logger4.logger.debug("No job to run");
|
|
1117
|
+
didMiss = true;
|
|
1118
|
+
break;
|
|
1119
|
+
}
|
|
1120
|
+
this.startExecution(executeConfig, workersInstance, jobToRun);
|
|
1121
|
+
if (!workersInstance.running) break;
|
|
1122
|
+
}
|
|
970
1123
|
} catch (error) {
|
|
971
|
-
import_logger4.logger.error("Error in job
|
|
972
|
-
|
|
1124
|
+
import_logger4.logger.error("Error in job scheduler.", { error });
|
|
1125
|
+
didMiss = true;
|
|
1126
|
+
}
|
|
1127
|
+
if (!workersInstance.running) return;
|
|
1128
|
+
const concurrencyIsFull = workersInstance.activeExecutions.size >= config.workersCount && workersInstance.activeExecutions.size > 0;
|
|
1129
|
+
if (concurrencyIsFull || allJobNamesAtCapacity) {
|
|
1130
|
+
await this.waitForCapacityOrStop(workersInstance);
|
|
1131
|
+
} else if (didMiss || workersInstance.activeExecutions.size === 0) {
|
|
1132
|
+
await this.waitForPollOrStop(config, workersInstance);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
getOtherJobAcquisitionHint(hint) {
|
|
1137
|
+
return hint === INITIAL_JOB_ACQUISITION_HINT ? CANDIDATE_JOB_ACQUISITION_HINT : INITIAL_JOB_ACQUISITION_HINT;
|
|
1138
|
+
}
|
|
1139
|
+
getMedian(values) {
|
|
1140
|
+
const sortedValues = [...values].sort((left, right) => left - right);
|
|
1141
|
+
return sortedValues[Math.floor(sortedValues.length / 2)];
|
|
1142
|
+
}
|
|
1143
|
+
resetPendingJobAcquisitionHint(workersInstance) {
|
|
1144
|
+
workersInstance.pendingJobAcquisitionHint = void 0;
|
|
1145
|
+
workersInstance.pendingJobAcquisitionHintWins = 0;
|
|
1146
|
+
}
|
|
1147
|
+
async runJobAcquisitionHintProbe(config, workersInstance) {
|
|
1148
|
+
const jobNames = this.getJobNames(config.jobs);
|
|
1149
|
+
if (jobNames.length === 0 || !workersInstance.running) return;
|
|
1150
|
+
const byHint = {
|
|
1151
|
+
[INITIAL_JOB_ACQUISITION_HINT]: [],
|
|
1152
|
+
[CANDIDATE_JOB_ACQUISITION_HINT]: []
|
|
1153
|
+
};
|
|
1154
|
+
const startsWithInitialHint = workersInstance.jobAcquisitionHintProbeCycle % 2 === 0;
|
|
1155
|
+
const firstHint = startsWithInitialHint ? INITIAL_JOB_ACQUISITION_HINT : CANDIDATE_JOB_ACQUISITION_HINT;
|
|
1156
|
+
const secondHint = this.getOtherJobAcquisitionHint(firstHint);
|
|
1157
|
+
workersInstance.jobAcquisitionHintProbeCycle++;
|
|
1158
|
+
try {
|
|
1159
|
+
for (let sample = 0; sample < 3; sample++) {
|
|
1160
|
+
for (const hint of [firstHint, secondHint]) {
|
|
1161
|
+
if (!workersInstance.running) return;
|
|
1162
|
+
const executionTimeMillis = await this.jobsRepo.getJobAcquisitionExecutionTime(
|
|
1163
|
+
jobNames,
|
|
1164
|
+
hint
|
|
1165
|
+
);
|
|
1166
|
+
if (!workersInstance.running) return;
|
|
1167
|
+
byHint[hint].push(executionTimeMillis);
|
|
1168
|
+
}
|
|
973
1169
|
}
|
|
1170
|
+
} catch (error) {
|
|
1171
|
+
this.resetPendingJobAcquisitionHint(workersInstance);
|
|
1172
|
+
if (workersInstance.running) {
|
|
1173
|
+
import_logger4.logger.warn("Job acquisition hint probe failed; keeping the current hint.", { error });
|
|
1174
|
+
}
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
const currentHint = workersInstance.jobAcquisitionHint;
|
|
1178
|
+
const otherHint = this.getOtherJobAcquisitionHint(currentHint);
|
|
1179
|
+
const currentMedian = this.getMedian(byHint[currentHint]);
|
|
1180
|
+
const otherMedian = this.getMedian(byHint[otherHint]);
|
|
1181
|
+
const winner = otherMedian < currentMedian ? otherHint : currentHint;
|
|
1182
|
+
if (!workersInstance.hasCompletedInitialJobAcquisitionHintProbe) {
|
|
1183
|
+
workersInstance.hasCompletedInitialJobAcquisitionHintProbe = true;
|
|
1184
|
+
workersInstance.jobAcquisitionHint = winner;
|
|
1185
|
+
this.resetPendingJobAcquisitionHint(workersInstance);
|
|
1186
|
+
import_logger4.logger.info(`Selected job acquisition hint "${winner}" after startup probe.`);
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
if (winner === currentHint) {
|
|
1190
|
+
this.resetPendingJobAcquisitionHint(workersInstance);
|
|
1191
|
+
import_logger4.logger.debug(`Keeping job acquisition hint "${currentHint}" after probe.`);
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
if (workersInstance.pendingJobAcquisitionHint === winner) {
|
|
1195
|
+
workersInstance.pendingJobAcquisitionHintWins++;
|
|
1196
|
+
} else {
|
|
1197
|
+
workersInstance.pendingJobAcquisitionHint = winner;
|
|
1198
|
+
workersInstance.pendingJobAcquisitionHintWins = 1;
|
|
1199
|
+
}
|
|
1200
|
+
if (workersInstance.pendingJobAcquisitionHintWins < 2) {
|
|
1201
|
+
import_logger4.logger.debug(
|
|
1202
|
+
`Keeping job acquisition hint "${currentHint}"; "${winner}" has one consecutive probe win.`
|
|
1203
|
+
);
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
workersInstance.jobAcquisitionHint = winner;
|
|
1207
|
+
this.resetPendingJobAcquisitionHint(workersInstance);
|
|
1208
|
+
import_logger4.logger.info(`Changed job acquisition hint from "${currentHint}" to "${winner}".`);
|
|
1209
|
+
}
|
|
1210
|
+
async waitForNextJobAcquisitionHintProbe(workersInstance) {
|
|
1211
|
+
await new Promise((resolve) => {
|
|
1212
|
+
let resolved = false;
|
|
1213
|
+
const finishWait = () => {
|
|
1214
|
+
if (resolved) return;
|
|
1215
|
+
resolved = true;
|
|
1216
|
+
clearTimeout(workersInstance.jobAcquisitionHintProbeTimer);
|
|
1217
|
+
workersInstance.jobAcquisitionHintProbeTimer = void 0;
|
|
1218
|
+
workersInstance.cancelJobAcquisitionHintProbeWait = void 0;
|
|
1219
|
+
resolve();
|
|
1220
|
+
};
|
|
1221
|
+
workersInstance.jobAcquisitionHintProbeTimer = setTimeout(
|
|
1222
|
+
finishWait,
|
|
1223
|
+
this.jobAcquisitionHintProbeIntervalMs
|
|
1224
|
+
);
|
|
1225
|
+
workersInstance.cancelJobAcquisitionHintProbeWait = finishWait;
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
async runJobAcquisitionHintProbeLoop(config, workersInstance) {
|
|
1229
|
+
if (this.getJobNames(config.jobs).length === 0) return;
|
|
1230
|
+
while (workersInstance.running) {
|
|
1231
|
+
await this.runJobAcquisitionHintProbe(config, workersInstance);
|
|
1232
|
+
if (!workersInstance.running) return;
|
|
1233
|
+
await this.waitForNextJobAcquisitionHintProbe(workersInstance);
|
|
974
1234
|
}
|
|
975
1235
|
}
|
|
976
1236
|
createWorkersInstanceDefinition(config) {
|
|
1237
|
+
let signalStop;
|
|
1238
|
+
const stopSignal = new Promise((resolve) => {
|
|
1239
|
+
signalStop = resolve;
|
|
1240
|
+
});
|
|
977
1241
|
const workersInstance = {
|
|
978
1242
|
running: true,
|
|
979
1243
|
workersCount: config.workersCount,
|
|
980
|
-
|
|
1244
|
+
activeExecutions: /* @__PURE__ */ new Map(),
|
|
1245
|
+
staleExecutions: /* @__PURE__ */ new Map(),
|
|
981
1246
|
runningJobsByName: /* @__PURE__ */ new Map(),
|
|
982
|
-
|
|
1247
|
+
jobAcquisitionHint: INITIAL_JOB_ACQUISITION_HINT,
|
|
1248
|
+
jobAcquisitionHintProbeCycle: 0,
|
|
1249
|
+
hasCompletedInitialJobAcquisitionHintProbe: false,
|
|
1250
|
+
pendingJobAcquisitionHintWins: 0,
|
|
1251
|
+
jobAcquisitionHintProbePromise: Promise.resolve(),
|
|
1252
|
+
schedulerPromise: Promise.resolve(),
|
|
1253
|
+
stopSignal,
|
|
1254
|
+
signalStop,
|
|
1255
|
+
get runningExecutions() {
|
|
1256
|
+
return this.activeExecutions.size;
|
|
1257
|
+
},
|
|
983
1258
|
stop: async () => {
|
|
984
|
-
|
|
985
|
-
workersInstance.
|
|
986
|
-
|
|
987
|
-
|
|
1259
|
+
if (workersInstance.stopPromise) return workersInstance.stopPromise;
|
|
1260
|
+
workersInstance.stopPromise = (async () => {
|
|
1261
|
+
var _a;
|
|
1262
|
+
import_logger4.logger.info("Stopping job scheduler...");
|
|
1263
|
+
workersInstance.running = false;
|
|
1264
|
+
workersInstance.signalStop();
|
|
1265
|
+
(_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
|
|
1266
|
+
await Promise.all([
|
|
1267
|
+
workersInstance.schedulerPromise,
|
|
1268
|
+
workersInstance.jobAcquisitionHintProbePromise
|
|
1269
|
+
]);
|
|
1270
|
+
const activeCapacity = Array.from(
|
|
1271
|
+
workersInstance.activeExecutions.values(),
|
|
1272
|
+
(execution) => execution.capacityPromise
|
|
1273
|
+
);
|
|
1274
|
+
await Promise.all(activeCapacity);
|
|
1275
|
+
})();
|
|
1276
|
+
return workersInstance.stopPromise;
|
|
988
1277
|
}
|
|
989
1278
|
};
|
|
990
1279
|
return workersInstance;
|
|
@@ -998,44 +1287,17 @@ var WorkerService = class {
|
|
|
998
1287
|
})
|
|
999
1288
|
);
|
|
1000
1289
|
}
|
|
1001
|
-
async
|
|
1002
|
-
if (!workersInstance.running) {
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
|
-
const workerInstance = {
|
|
1006
|
-
running: true,
|
|
1007
|
-
workerIndex,
|
|
1008
|
-
stop: async () => {
|
|
1009
|
-
import_logger4.logger.info(`Stopping worker [w${workerIndex}]...`);
|
|
1010
|
-
workerInstance.running = false;
|
|
1011
|
-
await workerInstance.promise;
|
|
1012
|
-
},
|
|
1013
|
-
respawn: async () => {
|
|
1014
|
-
import_logger4.logger.info(`Respawning worker [w${workerIndex}]...`);
|
|
1015
|
-
workerInstance.running = false;
|
|
1016
|
-
await this.startANewWorker(config, workersInstance, workerIndex);
|
|
1017
|
-
}
|
|
1018
|
-
};
|
|
1019
|
-
const workerPromise = this.startWorker(config, workersInstance, workerInstance);
|
|
1020
|
-
workerInstance.promise = workerPromise;
|
|
1021
|
-
workersInstance.workers[workerIndex] = workerInstance;
|
|
1022
|
-
}
|
|
1023
|
-
async runWorkers(config, workersInstance) {
|
|
1290
|
+
async runScheduler(config, workersInstance) {
|
|
1024
1291
|
import_logger4.logger.debug("Will ensure maxTriesReached retention and TTL index");
|
|
1025
|
-
|
|
1292
|
+
void this.jobsRepo.ensureMaxTriesReachedRetention(config.maxTriesReachedRetentionMs).catch((error) => import_logger4.logger.error("Error ensuring maxTriesReached retention", { error }));
|
|
1026
1293
|
import_logger4.logger.debug("Will ensure records for recurrent jobs");
|
|
1027
1294
|
await this.ensureRecords(config);
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
import_logger4.logger.info(`Starting ${workersCount} ${workerWord}`);
|
|
1031
|
-
for (let workerIndex = 0; workerIndex < workersCount; workerIndex++) {
|
|
1032
|
-
this.startANewWorker(config, workersInstance, workerIndex);
|
|
1033
|
-
}
|
|
1034
|
-
await ensureRetentionPromise;
|
|
1295
|
+
import_logger4.logger.info(`Starting job scheduler with concurrency ${config.workersCount}`);
|
|
1296
|
+
await this.runSchedulerLoop(config, workersInstance);
|
|
1035
1297
|
}
|
|
1036
1298
|
/**
|
|
1037
|
-
* Starts
|
|
1038
|
-
* @param userConfig - Configuration for the
|
|
1299
|
+
* Starts one job scheduler with the provided concurrency configuration.
|
|
1300
|
+
* @param userConfig - Configuration for the scheduler. Required field: jobs
|
|
1039
1301
|
*/
|
|
1040
1302
|
startWorkers(userConfig) {
|
|
1041
1303
|
const maxTriesReachedRetentionMs = userConfig.maxTriesReachedRetentionMs === void 0 ? DEFAULT_MAX_TRIES_REACHED_RETENTION_MS : userConfig.maxTriesReachedRetentionMs;
|
|
@@ -1043,7 +1305,6 @@ var WorkerService = class {
|
|
|
1043
1305
|
throw new Error("maxTriesReachedRetentionMs must be null, zero, or a positive number");
|
|
1044
1306
|
}
|
|
1045
1307
|
const config = {
|
|
1046
|
-
cooldownPeriod: 100,
|
|
1047
1308
|
pollInterval: 3e3,
|
|
1048
1309
|
workersCount: 4,
|
|
1049
1310
|
defaultLockTime: 30 * 1e3,
|
|
@@ -1052,8 +1313,18 @@ var WorkerService = class {
|
|
|
1052
1313
|
};
|
|
1053
1314
|
setNameToJobs(config.jobs);
|
|
1054
1315
|
const workersInstance = this.createWorkersInstanceDefinition(config);
|
|
1055
|
-
import_logger4.logger.debug("Starting
|
|
1056
|
-
this.
|
|
1316
|
+
import_logger4.logger.debug("Starting job scheduler", config);
|
|
1317
|
+
workersInstance.jobAcquisitionHintProbePromise = this.runJobAcquisitionHintProbeLoop(
|
|
1318
|
+
config,
|
|
1319
|
+
workersInstance
|
|
1320
|
+
);
|
|
1321
|
+
workersInstance.schedulerPromise = this.runScheduler(config, workersInstance).catch((error) => {
|
|
1322
|
+
var _a;
|
|
1323
|
+
workersInstance.running = false;
|
|
1324
|
+
workersInstance.signalStop();
|
|
1325
|
+
(_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
|
|
1326
|
+
import_logger4.logger.error("Job scheduler stopped after an unexpected error.", { error });
|
|
1327
|
+
});
|
|
1057
1328
|
return workersInstance;
|
|
1058
1329
|
}
|
|
1059
1330
|
};
|