@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.js
CHANGED
|
@@ -98,6 +98,35 @@ var JobRecordSchema = schemaWithName("JobRecord", {
|
|
|
98
98
|
var DEFAULT_MAX_TRIES_REACHED_RETENTION_MS = 1e3 * 60 * 60 * 24 * 7;
|
|
99
99
|
|
|
100
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
|
+
}
|
|
101
130
|
var _jobs_dec, _JobsRepo_decorators, _init;
|
|
102
131
|
_JobsRepo_decorators = [Repository()], _jobs_dec = [MongoCollection({
|
|
103
132
|
idGeneration: "uuid",
|
|
@@ -105,11 +134,10 @@ _JobsRepo_decorators = [Repository()], _jobs_dec = [MongoCollection({
|
|
|
105
134
|
schema: JobRecordSchema,
|
|
106
135
|
indexes: [
|
|
107
136
|
{
|
|
108
|
-
keys:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
137
|
+
keys: JOB_ACQUISITION_HINTS.byJobName
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
keys: JOB_ACQUISITION_HINTS.globalPriority
|
|
113
141
|
},
|
|
114
142
|
{
|
|
115
143
|
keys: {
|
|
@@ -142,30 +170,20 @@ var JobsRepo = class {
|
|
|
142
170
|
constructor() {
|
|
143
171
|
__publicField(this, "jobs", __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
|
|
144
172
|
}
|
|
145
|
-
async getJobAndLock(jobNames, lockTime) {
|
|
173
|
+
async getJobAndLock(jobNames, lockTime, hint = INITIAL_JOB_ACQUISITION_HINT) {
|
|
146
174
|
const executionId = generateId();
|
|
147
|
-
const
|
|
175
|
+
const now = /* @__PURE__ */ new Date();
|
|
176
|
+
const lockedUntil = new Date(now.getTime() + lockTime);
|
|
148
177
|
const job = await this.jobs.findOneAndUpdate(
|
|
178
|
+
getJobAcquisitionSelector(jobNames, now),
|
|
149
179
|
{
|
|
150
|
-
|
|
151
|
-
nextRunAt: { $lte: /* @__PURE__ */ new Date() },
|
|
152
|
-
$and: [
|
|
153
|
-
{ $or: [{ lockedUntil: { $exists: false } }, { lockedUntil: { $lte: /* @__PURE__ */ new Date() } }] },
|
|
154
|
-
// Exclude event jobs that have reached max tries. Recurrent jobs keep running even
|
|
155
|
-
// if old records still have this status from previous versions.
|
|
156
|
-
{ $or: [{ type: { $ne: "event" } }, { status: { $ne: "maxTriesReached" } }] }
|
|
157
|
-
]
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
$set: { lockId: executionId, lockedUntil, lastRunAt: /* @__PURE__ */ new Date() },
|
|
180
|
+
$set: { lockId: executionId, lockedUntil, lastRunAt: now },
|
|
161
181
|
$inc: { tries: 1 }
|
|
162
182
|
},
|
|
163
183
|
{
|
|
164
184
|
mongoOptions: {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
nextRunAt: 1
|
|
168
|
-
},
|
|
185
|
+
hint: JOB_ACQUISITION_HINTS[hint],
|
|
186
|
+
sort: JOB_ACQUISITION_SORT,
|
|
169
187
|
returnDocument: "before"
|
|
170
188
|
}
|
|
171
189
|
}
|
|
@@ -190,6 +208,19 @@ var JobsRepo = class {
|
|
|
190
208
|
wasStale
|
|
191
209
|
};
|
|
192
210
|
}
|
|
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
|
+
}
|
|
193
224
|
async setJobRecordPriority(jobId, priority, lockId) {
|
|
194
225
|
const result = await this.jobs.updateOne(lockId ? { _id: jobId, lockId } : jobId, {
|
|
195
226
|
$set: { priority }
|
|
@@ -928,6 +959,7 @@ var _executor_dec, _jobsRepo_dec3, _WorkerService_decorators, _init5;
|
|
|
928
959
|
_WorkerService_decorators = [Service3()], _jobsRepo_dec3 = [Inject3(() => JobsRepo)], _executor_dec = [Inject3(() => Executor)];
|
|
929
960
|
var WorkerService = class {
|
|
930
961
|
constructor() {
|
|
962
|
+
__publicField(this, "jobAcquisitionHintProbeIntervalMs", 30 * 60 * 1e3);
|
|
931
963
|
__publicField(this, "jobsRepo", __runInitializers(_init5, 8, this)), __runInitializers(_init5, 11, this);
|
|
932
964
|
__publicField(this, "executor", __runInitializers(_init5, 12, this)), __runInitializers(_init5, 15, this);
|
|
933
965
|
}
|
|
@@ -1033,7 +1065,8 @@ var WorkerService = class {
|
|
|
1033
1065
|
}
|
|
1034
1066
|
const jobToRun = await this.jobsRepo.getJobAndLock(
|
|
1035
1067
|
availableJobNames,
|
|
1036
|
-
config.defaultLockTime
|
|
1068
|
+
config.defaultLockTime,
|
|
1069
|
+
workersInstance.jobAcquisitionHint
|
|
1037
1070
|
);
|
|
1038
1071
|
if (!jobToRun) {
|
|
1039
1072
|
logger4.debug("No job to run");
|
|
@@ -1056,6 +1089,106 @@ var WorkerService = class {
|
|
|
1056
1089
|
}
|
|
1057
1090
|
}
|
|
1058
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
|
+
}
|
|
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);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1059
1192
|
createWorkersInstanceDefinition(config) {
|
|
1060
1193
|
let signalStop;
|
|
1061
1194
|
const stopSignal = new Promise((resolve) => {
|
|
@@ -1067,6 +1200,11 @@ var WorkerService = class {
|
|
|
1067
1200
|
activeExecutions: /* @__PURE__ */ new Map(),
|
|
1068
1201
|
staleExecutions: /* @__PURE__ */ new Map(),
|
|
1069
1202
|
runningJobsByName: /* @__PURE__ */ new Map(),
|
|
1203
|
+
jobAcquisitionHint: INITIAL_JOB_ACQUISITION_HINT,
|
|
1204
|
+
jobAcquisitionHintProbeCycle: 0,
|
|
1205
|
+
hasCompletedInitialJobAcquisitionHintProbe: false,
|
|
1206
|
+
pendingJobAcquisitionHintWins: 0,
|
|
1207
|
+
jobAcquisitionHintProbePromise: Promise.resolve(),
|
|
1070
1208
|
schedulerPromise: Promise.resolve(),
|
|
1071
1209
|
stopSignal,
|
|
1072
1210
|
signalStop,
|
|
@@ -1076,10 +1214,15 @@ var WorkerService = class {
|
|
|
1076
1214
|
stop: async () => {
|
|
1077
1215
|
if (workersInstance.stopPromise) return workersInstance.stopPromise;
|
|
1078
1216
|
workersInstance.stopPromise = (async () => {
|
|
1217
|
+
var _a;
|
|
1079
1218
|
logger4.info("Stopping job scheduler...");
|
|
1080
1219
|
workersInstance.running = false;
|
|
1081
1220
|
workersInstance.signalStop();
|
|
1082
|
-
|
|
1221
|
+
(_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
|
|
1222
|
+
await Promise.all([
|
|
1223
|
+
workersInstance.schedulerPromise,
|
|
1224
|
+
workersInstance.jobAcquisitionHintProbePromise
|
|
1225
|
+
]);
|
|
1083
1226
|
const activeCapacity = Array.from(
|
|
1084
1227
|
workersInstance.activeExecutions.values(),
|
|
1085
1228
|
(execution) => execution.capacityPromise
|
|
@@ -1127,8 +1270,15 @@ var WorkerService = class {
|
|
|
1127
1270
|
setNameToJobs(config.jobs);
|
|
1128
1271
|
const workersInstance = this.createWorkersInstanceDefinition(config);
|
|
1129
1272
|
logger4.debug("Starting job scheduler", config);
|
|
1273
|
+
workersInstance.jobAcquisitionHintProbePromise = this.runJobAcquisitionHintProbeLoop(
|
|
1274
|
+
config,
|
|
1275
|
+
workersInstance
|
|
1276
|
+
);
|
|
1130
1277
|
workersInstance.schedulerPromise = this.runScheduler(config, workersInstance).catch((error) => {
|
|
1278
|
+
var _a;
|
|
1131
1279
|
workersInstance.running = false;
|
|
1280
|
+
workersInstance.signalStop();
|
|
1281
|
+
(_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
|
|
1132
1282
|
logger4.error("Job scheduler stopped after an unexpected error.", { error });
|
|
1133
1283
|
});
|
|
1134
1284
|
return workersInstance;
|