@orion-js/dogs 4.6.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 +174 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +174 -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 +1 -1
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 }
|
|
@@ -972,6 +1003,7 @@ var _executor_dec, _jobsRepo_dec3, _WorkerService_decorators, _init5;
|
|
|
972
1003
|
_WorkerService_decorators = [(0, import_services3.Service)()], _jobsRepo_dec3 = [(0, import_services3.Inject)(() => JobsRepo)], _executor_dec = [(0, import_services3.Inject)(() => Executor)];
|
|
973
1004
|
var WorkerService = class {
|
|
974
1005
|
constructor() {
|
|
1006
|
+
__publicField(this, "jobAcquisitionHintProbeIntervalMs", 30 * 60 * 1e3);
|
|
975
1007
|
__publicField(this, "jobsRepo", __runInitializers(_init5, 8, this)), __runInitializers(_init5, 11, this);
|
|
976
1008
|
__publicField(this, "executor", __runInitializers(_init5, 12, this)), __runInitializers(_init5, 15, this);
|
|
977
1009
|
}
|
|
@@ -1077,7 +1109,8 @@ var WorkerService = class {
|
|
|
1077
1109
|
}
|
|
1078
1110
|
const jobToRun = await this.jobsRepo.getJobAndLock(
|
|
1079
1111
|
availableJobNames,
|
|
1080
|
-
config.defaultLockTime
|
|
1112
|
+
config.defaultLockTime,
|
|
1113
|
+
workersInstance.jobAcquisitionHint
|
|
1081
1114
|
);
|
|
1082
1115
|
if (!jobToRun) {
|
|
1083
1116
|
import_logger4.logger.debug("No job to run");
|
|
@@ -1100,6 +1133,106 @@ var WorkerService = class {
|
|
|
1100
1133
|
}
|
|
1101
1134
|
}
|
|
1102
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
|
+
}
|
|
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);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1103
1236
|
createWorkersInstanceDefinition(config) {
|
|
1104
1237
|
let signalStop;
|
|
1105
1238
|
const stopSignal = new Promise((resolve) => {
|
|
@@ -1111,6 +1244,11 @@ var WorkerService = class {
|
|
|
1111
1244
|
activeExecutions: /* @__PURE__ */ new Map(),
|
|
1112
1245
|
staleExecutions: /* @__PURE__ */ new Map(),
|
|
1113
1246
|
runningJobsByName: /* @__PURE__ */ new Map(),
|
|
1247
|
+
jobAcquisitionHint: INITIAL_JOB_ACQUISITION_HINT,
|
|
1248
|
+
jobAcquisitionHintProbeCycle: 0,
|
|
1249
|
+
hasCompletedInitialJobAcquisitionHintProbe: false,
|
|
1250
|
+
pendingJobAcquisitionHintWins: 0,
|
|
1251
|
+
jobAcquisitionHintProbePromise: Promise.resolve(),
|
|
1114
1252
|
schedulerPromise: Promise.resolve(),
|
|
1115
1253
|
stopSignal,
|
|
1116
1254
|
signalStop,
|
|
@@ -1120,10 +1258,15 @@ var WorkerService = class {
|
|
|
1120
1258
|
stop: async () => {
|
|
1121
1259
|
if (workersInstance.stopPromise) return workersInstance.stopPromise;
|
|
1122
1260
|
workersInstance.stopPromise = (async () => {
|
|
1261
|
+
var _a;
|
|
1123
1262
|
import_logger4.logger.info("Stopping job scheduler...");
|
|
1124
1263
|
workersInstance.running = false;
|
|
1125
1264
|
workersInstance.signalStop();
|
|
1126
|
-
|
|
1265
|
+
(_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
|
|
1266
|
+
await Promise.all([
|
|
1267
|
+
workersInstance.schedulerPromise,
|
|
1268
|
+
workersInstance.jobAcquisitionHintProbePromise
|
|
1269
|
+
]);
|
|
1127
1270
|
const activeCapacity = Array.from(
|
|
1128
1271
|
workersInstance.activeExecutions.values(),
|
|
1129
1272
|
(execution) => execution.capacityPromise
|
|
@@ -1171,8 +1314,15 @@ var WorkerService = class {
|
|
|
1171
1314
|
setNameToJobs(config.jobs);
|
|
1172
1315
|
const workersInstance = this.createWorkersInstanceDefinition(config);
|
|
1173
1316
|
import_logger4.logger.debug("Starting job scheduler", config);
|
|
1317
|
+
workersInstance.jobAcquisitionHintProbePromise = this.runJobAcquisitionHintProbeLoop(
|
|
1318
|
+
config,
|
|
1319
|
+
workersInstance
|
|
1320
|
+
);
|
|
1174
1321
|
workersInstance.schedulerPromise = this.runScheduler(config, workersInstance).catch((error) => {
|
|
1322
|
+
var _a;
|
|
1175
1323
|
workersInstance.running = false;
|
|
1324
|
+
workersInstance.signalStop();
|
|
1325
|
+
(_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
|
|
1176
1326
|
import_logger4.logger.error("Job scheduler stopped after an unexpected error.", { error });
|
|
1177
1327
|
});
|
|
1178
1328
|
return workersInstance;
|