@orion-js/dogs 4.6.1 → 4.6.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/index.cjs +176 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +176 -24
- package/dist/index.js.map +1 -1
- package/dist/repos/JobsRepo.d.ts +16 -1
- package/dist/services/WorkerService.d.ts +16 -0
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -142,6 +142,35 @@ var JobRecordSchema = (0, import_schema.schemaWithName)("JobRecord", {
|
|
|
142
142
|
var DEFAULT_MAX_TRIES_REACHED_RETENTION_MS = 1e3 * 60 * 60 * 24 * 7;
|
|
143
143
|
|
|
144
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
|
+
}
|
|
145
174
|
var _jobs_dec, _JobsRepo_decorators, _init;
|
|
146
175
|
_JobsRepo_decorators = [(0, import_mongodb.Repository)()], _jobs_dec = [(0, import_mongodb.MongoCollection)({
|
|
147
176
|
idGeneration: "uuid",
|
|
@@ -149,11 +178,10 @@ _JobsRepo_decorators = [(0, import_mongodb.Repository)()], _jobs_dec = [(0, impo
|
|
|
149
178
|
schema: JobRecordSchema,
|
|
150
179
|
indexes: [
|
|
151
180
|
{
|
|
152
|
-
keys:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
181
|
+
keys: JOB_ACQUISITION_HINTS.byJobName
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
keys: JOB_ACQUISITION_HINTS.globalPriority
|
|
157
185
|
},
|
|
158
186
|
{
|
|
159
187
|
keys: {
|
|
@@ -186,30 +214,20 @@ var JobsRepo = class {
|
|
|
186
214
|
constructor() {
|
|
187
215
|
__publicField(this, "jobs", __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
|
|
188
216
|
}
|
|
189
|
-
async getJobAndLock(jobNames, lockTime) {
|
|
217
|
+
async getJobAndLock(jobNames, lockTime, hint = INITIAL_JOB_ACQUISITION_HINT) {
|
|
190
218
|
const executionId = (0, import_helpers.generateId)();
|
|
191
|
-
const
|
|
219
|
+
const now = /* @__PURE__ */ new Date();
|
|
220
|
+
const lockedUntil = new Date(now.getTime() + lockTime);
|
|
192
221
|
const job = await this.jobs.findOneAndUpdate(
|
|
222
|
+
getJobAcquisitionSelector(jobNames, now),
|
|
193
223
|
{
|
|
194
|
-
|
|
195
|
-
nextRunAt: { $lte: /* @__PURE__ */ new Date() },
|
|
196
|
-
$and: [
|
|
197
|
-
{ $or: [{ lockedUntil: { $exists: false } }, { lockedUntil: { $lte: /* @__PURE__ */ new Date() } }] },
|
|
198
|
-
// Exclude event jobs that have reached max tries. Recurrent jobs keep running even
|
|
199
|
-
// if old records still have this status from previous versions.
|
|
200
|
-
{ $or: [{ type: { $ne: "event" } }, { status: { $ne: "maxTriesReached" } }] }
|
|
201
|
-
]
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
$set: { lockId: executionId, lockedUntil, lastRunAt: /* @__PURE__ */ new Date() },
|
|
224
|
+
$set: { lockId: executionId, lockedUntil, lastRunAt: now },
|
|
205
225
|
$inc: { tries: 1 }
|
|
206
226
|
},
|
|
207
227
|
{
|
|
208
228
|
mongoOptions: {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
nextRunAt: 1
|
|
212
|
-
},
|
|
229
|
+
hint: JOB_ACQUISITION_HINTS[hint],
|
|
230
|
+
sort: JOB_ACQUISITION_SORT,
|
|
213
231
|
returnDocument: "before"
|
|
214
232
|
}
|
|
215
233
|
}
|
|
@@ -234,6 +252,19 @@ var JobsRepo = class {
|
|
|
234
252
|
wasStale
|
|
235
253
|
};
|
|
236
254
|
}
|
|
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
|
+
}
|
|
237
268
|
async setJobRecordPriority(jobId, priority, lockId) {
|
|
238
269
|
const result = await this.jobs.updateOne(lockId ? { _id: jobId, lockId } : jobId, {
|
|
239
270
|
$set: { priority }
|
|
@@ -968,10 +999,12 @@ Executor = __decorateElement(_init4, 0, "Executor", _Executor_decorators, Execut
|
|
|
968
999
|
__runInitializers(_init4, 1, Executor);
|
|
969
1000
|
|
|
970
1001
|
// src/services/WorkerService.ts
|
|
1002
|
+
var FILTERED_JOB_NAMES_ACQUISITION_HINT = "byJobName";
|
|
971
1003
|
var _executor_dec, _jobsRepo_dec3, _WorkerService_decorators, _init5;
|
|
972
1004
|
_WorkerService_decorators = [(0, import_services3.Service)()], _jobsRepo_dec3 = [(0, import_services3.Inject)(() => JobsRepo)], _executor_dec = [(0, import_services3.Inject)(() => Executor)];
|
|
973
1005
|
var WorkerService = class {
|
|
974
1006
|
constructor() {
|
|
1007
|
+
__publicField(this, "jobAcquisitionHintProbeIntervalMs", 30 * 60 * 1e3);
|
|
975
1008
|
__publicField(this, "jobsRepo", __runInitializers(_init5, 8, this)), __runInitializers(_init5, 11, this);
|
|
976
1009
|
__publicField(this, "executor", __runInitializers(_init5, 12, this)), __runInitializers(_init5, 15, this);
|
|
977
1010
|
}
|
|
@@ -1075,9 +1108,11 @@ var WorkerService = class {
|
|
|
1075
1108
|
allJobNamesAtCapacity = workersInstance.activeExecutions.size > 0;
|
|
1076
1109
|
break;
|
|
1077
1110
|
}
|
|
1111
|
+
const jobAcquisitionHint = availableJobNames.length < jobNames.length ? FILTERED_JOB_NAMES_ACQUISITION_HINT : workersInstance.jobAcquisitionHint;
|
|
1078
1112
|
const jobToRun = await this.jobsRepo.getJobAndLock(
|
|
1079
1113
|
availableJobNames,
|
|
1080
|
-
config.defaultLockTime
|
|
1114
|
+
config.defaultLockTime,
|
|
1115
|
+
jobAcquisitionHint
|
|
1081
1116
|
);
|
|
1082
1117
|
if (!jobToRun) {
|
|
1083
1118
|
import_logger4.logger.debug("No job to run");
|
|
@@ -1100,6 +1135,106 @@ var WorkerService = class {
|
|
|
1100
1135
|
}
|
|
1101
1136
|
}
|
|
1102
1137
|
}
|
|
1138
|
+
getOtherJobAcquisitionHint(hint) {
|
|
1139
|
+
return hint === INITIAL_JOB_ACQUISITION_HINT ? CANDIDATE_JOB_ACQUISITION_HINT : INITIAL_JOB_ACQUISITION_HINT;
|
|
1140
|
+
}
|
|
1141
|
+
getMedian(values) {
|
|
1142
|
+
const sortedValues = [...values].sort((left, right) => left - right);
|
|
1143
|
+
return sortedValues[Math.floor(sortedValues.length / 2)];
|
|
1144
|
+
}
|
|
1145
|
+
resetPendingJobAcquisitionHint(workersInstance) {
|
|
1146
|
+
workersInstance.pendingJobAcquisitionHint = void 0;
|
|
1147
|
+
workersInstance.pendingJobAcquisitionHintWins = 0;
|
|
1148
|
+
}
|
|
1149
|
+
async runJobAcquisitionHintProbe(config, workersInstance) {
|
|
1150
|
+
const jobNames = this.getJobNames(config.jobs);
|
|
1151
|
+
if (jobNames.length === 0 || !workersInstance.running) return;
|
|
1152
|
+
const byHint = {
|
|
1153
|
+
[INITIAL_JOB_ACQUISITION_HINT]: [],
|
|
1154
|
+
[CANDIDATE_JOB_ACQUISITION_HINT]: []
|
|
1155
|
+
};
|
|
1156
|
+
const startsWithInitialHint = workersInstance.jobAcquisitionHintProbeCycle % 2 === 0;
|
|
1157
|
+
const firstHint = startsWithInitialHint ? INITIAL_JOB_ACQUISITION_HINT : CANDIDATE_JOB_ACQUISITION_HINT;
|
|
1158
|
+
const secondHint = this.getOtherJobAcquisitionHint(firstHint);
|
|
1159
|
+
workersInstance.jobAcquisitionHintProbeCycle++;
|
|
1160
|
+
try {
|
|
1161
|
+
for (let sample = 0; sample < 3; sample++) {
|
|
1162
|
+
for (const hint of [firstHint, secondHint]) {
|
|
1163
|
+
if (!workersInstance.running) return;
|
|
1164
|
+
const executionTimeMillis = await this.jobsRepo.getJobAcquisitionExecutionTime(
|
|
1165
|
+
jobNames,
|
|
1166
|
+
hint
|
|
1167
|
+
);
|
|
1168
|
+
if (!workersInstance.running) return;
|
|
1169
|
+
byHint[hint].push(executionTimeMillis);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
} catch (error) {
|
|
1173
|
+
this.resetPendingJobAcquisitionHint(workersInstance);
|
|
1174
|
+
if (workersInstance.running) {
|
|
1175
|
+
import_logger4.logger.warn("Job acquisition hint probe failed; keeping the current hint.", { error });
|
|
1176
|
+
}
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
const currentHint = workersInstance.jobAcquisitionHint;
|
|
1180
|
+
const otherHint = this.getOtherJobAcquisitionHint(currentHint);
|
|
1181
|
+
const currentMedian = this.getMedian(byHint[currentHint]);
|
|
1182
|
+
const otherMedian = this.getMedian(byHint[otherHint]);
|
|
1183
|
+
const winner = otherMedian < currentMedian ? otherHint : currentHint;
|
|
1184
|
+
if (!workersInstance.hasCompletedInitialJobAcquisitionHintProbe) {
|
|
1185
|
+
workersInstance.hasCompletedInitialJobAcquisitionHintProbe = true;
|
|
1186
|
+
workersInstance.jobAcquisitionHint = winner;
|
|
1187
|
+
this.resetPendingJobAcquisitionHint(workersInstance);
|
|
1188
|
+
import_logger4.logger.info(`Selected job acquisition hint "${winner}" after startup probe.`);
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
if (winner === currentHint) {
|
|
1192
|
+
this.resetPendingJobAcquisitionHint(workersInstance);
|
|
1193
|
+
import_logger4.logger.debug(`Keeping job acquisition hint "${currentHint}" after probe.`);
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
if (workersInstance.pendingJobAcquisitionHint === winner) {
|
|
1197
|
+
workersInstance.pendingJobAcquisitionHintWins++;
|
|
1198
|
+
} else {
|
|
1199
|
+
workersInstance.pendingJobAcquisitionHint = winner;
|
|
1200
|
+
workersInstance.pendingJobAcquisitionHintWins = 1;
|
|
1201
|
+
}
|
|
1202
|
+
if (workersInstance.pendingJobAcquisitionHintWins < 2) {
|
|
1203
|
+
import_logger4.logger.debug(
|
|
1204
|
+
`Keeping job acquisition hint "${currentHint}"; "${winner}" has one consecutive probe win.`
|
|
1205
|
+
);
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
workersInstance.jobAcquisitionHint = winner;
|
|
1209
|
+
this.resetPendingJobAcquisitionHint(workersInstance);
|
|
1210
|
+
import_logger4.logger.info(`Changed job acquisition hint from "${currentHint}" to "${winner}".`);
|
|
1211
|
+
}
|
|
1212
|
+
async waitForNextJobAcquisitionHintProbe(workersInstance) {
|
|
1213
|
+
await new Promise((resolve) => {
|
|
1214
|
+
let resolved = false;
|
|
1215
|
+
const finishWait = () => {
|
|
1216
|
+
if (resolved) return;
|
|
1217
|
+
resolved = true;
|
|
1218
|
+
clearTimeout(workersInstance.jobAcquisitionHintProbeTimer);
|
|
1219
|
+
workersInstance.jobAcquisitionHintProbeTimer = void 0;
|
|
1220
|
+
workersInstance.cancelJobAcquisitionHintProbeWait = void 0;
|
|
1221
|
+
resolve();
|
|
1222
|
+
};
|
|
1223
|
+
workersInstance.jobAcquisitionHintProbeTimer = setTimeout(
|
|
1224
|
+
finishWait,
|
|
1225
|
+
this.jobAcquisitionHintProbeIntervalMs
|
|
1226
|
+
);
|
|
1227
|
+
workersInstance.cancelJobAcquisitionHintProbeWait = finishWait;
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
async runJobAcquisitionHintProbeLoop(config, workersInstance) {
|
|
1231
|
+
if (this.getJobNames(config.jobs).length === 0) return;
|
|
1232
|
+
while (workersInstance.running) {
|
|
1233
|
+
await this.runJobAcquisitionHintProbe(config, workersInstance);
|
|
1234
|
+
if (!workersInstance.running) return;
|
|
1235
|
+
await this.waitForNextJobAcquisitionHintProbe(workersInstance);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1103
1238
|
createWorkersInstanceDefinition(config) {
|
|
1104
1239
|
let signalStop;
|
|
1105
1240
|
const stopSignal = new Promise((resolve) => {
|
|
@@ -1111,6 +1246,11 @@ var WorkerService = class {
|
|
|
1111
1246
|
activeExecutions: /* @__PURE__ */ new Map(),
|
|
1112
1247
|
staleExecutions: /* @__PURE__ */ new Map(),
|
|
1113
1248
|
runningJobsByName: /* @__PURE__ */ new Map(),
|
|
1249
|
+
jobAcquisitionHint: INITIAL_JOB_ACQUISITION_HINT,
|
|
1250
|
+
jobAcquisitionHintProbeCycle: 0,
|
|
1251
|
+
hasCompletedInitialJobAcquisitionHintProbe: false,
|
|
1252
|
+
pendingJobAcquisitionHintWins: 0,
|
|
1253
|
+
jobAcquisitionHintProbePromise: Promise.resolve(),
|
|
1114
1254
|
schedulerPromise: Promise.resolve(),
|
|
1115
1255
|
stopSignal,
|
|
1116
1256
|
signalStop,
|
|
@@ -1120,10 +1260,15 @@ var WorkerService = class {
|
|
|
1120
1260
|
stop: async () => {
|
|
1121
1261
|
if (workersInstance.stopPromise) return workersInstance.stopPromise;
|
|
1122
1262
|
workersInstance.stopPromise = (async () => {
|
|
1263
|
+
var _a;
|
|
1123
1264
|
import_logger4.logger.info("Stopping job scheduler...");
|
|
1124
1265
|
workersInstance.running = false;
|
|
1125
1266
|
workersInstance.signalStop();
|
|
1126
|
-
|
|
1267
|
+
(_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
|
|
1268
|
+
await Promise.all([
|
|
1269
|
+
workersInstance.schedulerPromise,
|
|
1270
|
+
workersInstance.jobAcquisitionHintProbePromise
|
|
1271
|
+
]);
|
|
1127
1272
|
const activeCapacity = Array.from(
|
|
1128
1273
|
workersInstance.activeExecutions.values(),
|
|
1129
1274
|
(execution) => execution.capacityPromise
|
|
@@ -1171,8 +1316,15 @@ var WorkerService = class {
|
|
|
1171
1316
|
setNameToJobs(config.jobs);
|
|
1172
1317
|
const workersInstance = this.createWorkersInstanceDefinition(config);
|
|
1173
1318
|
import_logger4.logger.debug("Starting job scheduler", config);
|
|
1319
|
+
workersInstance.jobAcquisitionHintProbePromise = this.runJobAcquisitionHintProbeLoop(
|
|
1320
|
+
config,
|
|
1321
|
+
workersInstance
|
|
1322
|
+
);
|
|
1174
1323
|
workersInstance.schedulerPromise = this.runScheduler(config, workersInstance).catch((error) => {
|
|
1324
|
+
var _a;
|
|
1175
1325
|
workersInstance.running = false;
|
|
1326
|
+
workersInstance.signalStop();
|
|
1327
|
+
(_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
|
|
1176
1328
|
import_logger4.logger.error("Job scheduler stopped after an unexpected error.", { error });
|
|
1177
1329
|
});
|
|
1178
1330
|
return workersInstance;
|