@orion-js/dogs 4.6.6 → 4.6.8

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.js CHANGED
@@ -480,6 +480,17 @@ var JobsRepo = class {
480
480
  );
481
481
  return result.matchedCount > 0;
482
482
  }
483
+ async rescheduleAfterExecutionGuardAcquisitionFailure(jobId, lockId, nextRunAt) {
484
+ const result = await this.jobs.updateOne(
485
+ { _id: jobId, lockId, tries: { $gte: 1 } },
486
+ {
487
+ $set: { nextRunAt },
488
+ $inc: { tries: -1 },
489
+ $unset: { lockId: "", lockedUntil: "", claimWasStale: "" }
490
+ }
491
+ );
492
+ return result.matchedCount > 0;
493
+ }
483
494
  async deleteEventJob(jobId, lockId) {
484
495
  const result = await this.jobs.deleteOne({
485
496
  _id: jobId,
@@ -850,39 +861,102 @@ var Executor = class {
850
861
  return job.lockTime ?? jobToRun.lockTime;
851
862
  }
852
863
  getContext(job, jobToRun, onStale, isExecutionActive) {
853
- var _a;
854
864
  const effectiveLockTime = this.getEffectiveLockTime(job, jobToRun);
855
- let staleTimeout = setTimeout(() => onStale(), effectiveLockTime);
856
- (_a = staleTimeout.unref) == null ? void 0 : _a.call(staleTimeout);
857
865
  const contextLogger = logger3.addMetadata({
858
866
  jobName: jobToRun.name,
859
867
  jobId: jobToRun.jobId
860
868
  });
869
+ let staleTimeout;
870
+ let watchdogCleared = false;
871
+ let staleReported = false;
872
+ let pendingExtraTime;
873
+ let renewalRunning = false;
874
+ let renewalPromise;
875
+ const clearCurrentStaleTimeout = () => {
876
+ if (staleTimeout === void 0) return;
877
+ clearTimeout(staleTimeout);
878
+ staleTimeout = void 0;
879
+ };
880
+ const reportStale = () => {
881
+ if (staleReported) return;
882
+ staleReported = true;
883
+ watchdogCleared = true;
884
+ pendingExtraTime = void 0;
885
+ clearCurrentStaleTimeout();
886
+ onStale();
887
+ };
888
+ const armStaleTimeout = (lockedUntil) => {
889
+ var _a;
890
+ if (watchdogCleared || staleReported || !isExecutionActive()) return;
891
+ clearCurrentStaleTimeout();
892
+ staleTimeout = setTimeout(
893
+ () => {
894
+ staleTimeout = void 0;
895
+ reportStale();
896
+ },
897
+ Math.max(0, lockedUntil - Date.now())
898
+ );
899
+ (_a = staleTimeout.unref) == null ? void 0 : _a.call(staleTimeout);
900
+ };
901
+ const runRenewals = async () => {
902
+ try {
903
+ while (pendingExtraTime !== void 0) {
904
+ const extraTime = pendingExtraTime;
905
+ pendingExtraTime = void 0;
906
+ if (watchdogCleared || staleReported || !isExecutionActive()) {
907
+ clearCurrentStaleTimeout();
908
+ return;
909
+ }
910
+ clearCurrentStaleTimeout();
911
+ const lockedUntil = Date.now() + extraTime;
912
+ let didExtend = false;
913
+ try {
914
+ didExtend = await this.jobsRepo.extendLockTime(
915
+ jobToRun.jobId,
916
+ extraTime,
917
+ jobToRun.lockId
918
+ );
919
+ } catch (error) {
920
+ reportStale();
921
+ throw error;
922
+ }
923
+ if (!didExtend) {
924
+ reportStale();
925
+ return;
926
+ }
927
+ if (watchdogCleared || staleReported || !isExecutionActive()) {
928
+ pendingExtraTime = void 0;
929
+ clearCurrentStaleTimeout();
930
+ return;
931
+ }
932
+ armStaleTimeout(lockedUntil);
933
+ }
934
+ } finally {
935
+ renewalRunning = false;
936
+ renewalPromise = void 0;
937
+ }
938
+ };
939
+ armStaleTimeout(Date.now() + effectiveLockTime);
861
940
  return {
862
941
  definition: job,
863
942
  record: jobToRun,
864
943
  tries: jobToRun.tries || 0,
865
- clearStaleTimeout: () => clearTimeout(staleTimeout),
866
- extendLockTime: async (extraTime) => {
867
- var _a2;
868
- if (!isExecutionActive()) {
944
+ clearStaleTimeout: () => {
945
+ watchdogCleared = true;
946
+ pendingExtraTime = void 0;
947
+ clearCurrentStaleTimeout();
948
+ },
949
+ extendLockTime: (extraTime) => {
950
+ if (watchdogCleared || staleReported || !isExecutionActive()) {
869
951
  contextLogger.warn(`Will not extend lock for stale execution "${jobToRun.executionId}"`);
870
- return;
871
- }
872
- clearTimeout(staleTimeout);
873
- let didExtend = false;
874
- try {
875
- didExtend = await this.jobsRepo.extendLockTime(jobToRun.jobId, extraTime, jobToRun.lockId);
876
- } catch (error) {
877
- onStale();
878
- throw error;
952
+ return Promise.resolve();
879
953
  }
880
- if (!didExtend) {
881
- onStale();
882
- return;
954
+ pendingExtraTime = Math.max(pendingExtraTime ?? extraTime, extraTime);
955
+ if (!renewalRunning) {
956
+ renewalRunning = true;
957
+ renewalPromise = runRenewals();
883
958
  }
884
- staleTimeout = setTimeout(() => onStale(), extraTime);
885
- (_a2 = staleTimeout.unref) == null ? void 0 : _a2.call(staleTimeout);
959
+ return renewalPromise ?? Promise.resolve();
886
960
  },
887
961
  logger: contextLogger
888
962
  };
@@ -1017,6 +1091,42 @@ var Executor = class {
1017
1091
  }
1018
1092
  return false;
1019
1093
  }
1094
+ async handleExecutionGuardAcquisitionFailure(error, config, jobToRun, context) {
1095
+ context.logger.error(`Error acquiring execution guard for job "${jobToRun.name}"`, { error });
1096
+ const nextRunAt = new Date(Date.now() + (config.executionGuardRetryDelayMs ?? 1e3));
1097
+ try {
1098
+ const didReschedule = await this.jobsRepo.rescheduleAfterExecutionGuardAcquisitionFailure(
1099
+ jobToRun.jobId,
1100
+ jobToRun.lockId,
1101
+ nextRunAt
1102
+ );
1103
+ if (!didReschedule) {
1104
+ context.logger.warn(
1105
+ `Could not requeue job "${jobToRun.name}" after execution guard acquisition failed because its lock is no longer owned`
1106
+ );
1107
+ }
1108
+ } catch (rescheduleError) {
1109
+ context.logger.error(
1110
+ `Error requeueing job "${jobToRun.name}" after execution guard acquisition failed`,
1111
+ { error: rescheduleError }
1112
+ );
1113
+ }
1114
+ }
1115
+ async releaseExecutionGuardLease(lease, jobToRun, context) {
1116
+ try {
1117
+ await lease.release();
1118
+ } catch (error) {
1119
+ context.logger.error(`Error releasing execution guard for job "${jobToRun.name}"`, { error });
1120
+ }
1121
+ }
1122
+ async runGuardedResolver(job, jobToRun, context, lease) {
1123
+ try {
1124
+ return await job.resolve(jobToRun.params, context);
1125
+ } finally {
1126
+ context.clearStaleTimeout();
1127
+ await this.releaseExecutionGuardLease(lease, jobToRun, context);
1128
+ }
1129
+ }
1020
1130
  async executeJob(config, jobToRun, onExecutionStale) {
1021
1131
  const job = this.getJobDefinition(jobToRun, config.jobs);
1022
1132
  if (!job) return;
@@ -1098,13 +1208,36 @@ var Executor = class {
1098
1208
  params: jobToRun.params
1099
1209
  };
1100
1210
  await runWithOrionAsyncContext(extraContext, async () => {
1101
- try {
1211
+ let executionGuardLease;
1212
+ if (config.executionGuard) {
1102
1213
  updateOrionAsyncContext({
1103
1214
  jobName: jobToRun.name,
1104
1215
  jobId: jobToRun.jobId,
1105
1216
  params: jobToRun.params
1106
1217
  });
1107
- const result = await job.resolve(jobToRun.params, context);
1218
+ try {
1219
+ executionGuardLease = await config.executionGuard.acquire(jobToRun);
1220
+ } catch (error) {
1221
+ context.clearStaleTimeout();
1222
+ await this.handleExecutionGuardAcquisitionFailure(error, config, jobToRun, context);
1223
+ if (executionStatus === "stale") await staleHandlingPromise;
1224
+ return;
1225
+ }
1226
+ if (executionStatus === "stale") {
1227
+ await staleHandlingPromise;
1228
+ await this.releaseExecutionGuardLease(executionGuardLease, jobToRun, context);
1229
+ return;
1230
+ }
1231
+ }
1232
+ try {
1233
+ if (!config.executionGuard) {
1234
+ updateOrionAsyncContext({
1235
+ jobName: jobToRun.name,
1236
+ jobId: jobToRun.jobId,
1237
+ params: jobToRun.params
1238
+ });
1239
+ }
1240
+ const result = executionGuardLease ? await this.runGuardedResolver(job, jobToRun, context, executionGuardLease) : await job.resolve(jobToRun.params, context);
1108
1241
  context.clearStaleTimeout();
1109
1242
  if (executionStatus === "stale") {
1110
1243
  await staleHandlingPromise;
@@ -1283,7 +1416,11 @@ var WorkerService = class {
1283
1416
  jobs: config.jobs,
1284
1417
  maxTries: config.maxTries,
1285
1418
  maxTriesReachedRetentionMs: config.maxTriesReachedRetentionMs,
1286
- onMaxTriesReached: config.onMaxTriesReached
1419
+ onMaxTriesReached: config.onMaxTriesReached,
1420
+ ...config.executionGuard ? {
1421
+ executionGuard: config.executionGuard,
1422
+ executionGuardRetryDelayMs: Math.max(config.pollInterval, 1e3)
1423
+ } : {}
1287
1424
  };
1288
1425
  logger4.debug(`Running scheduler loop for jobs "${jobNames.join(", ")}"...`);
1289
1426
  let consecutivePartitionMisses = 0;