@dudousxd/nestjs-durable-core 0.85.1 → 0.85.3

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 CHANGED
@@ -2745,6 +2745,7 @@ async function runSchedules(engine, schedules, nowMs, opts) {
2745
2745
  if (prev && (prev.status === "running" || prev.status === "suspended")) continue;
2746
2746
  }
2747
2747
  }
2748
+ if (!(s.jitter && s.jitter > 0)) jitterFireAt.get(engine)?.delete(s.key);
2748
2749
  const firing = s.jitter && s.jitter > 0 ? jitterDue(engine, s, candidates, nowMs, random) : candidates;
2749
2750
  if (firing.length === 0) continue;
2750
2751
  for (const runId of firing) {
@@ -4036,7 +4037,6 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
4036
4037
  }));
4037
4038
  return unwrapCompletion(buffered.payload, `child "${id}"`);
4038
4039
  }
4039
- if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority, version);
4040
4040
  if (!existing) {
4041
4041
  await writeCheckpoint(instantCheckpoint({
4042
4042
  runId,
@@ -4046,6 +4046,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
4046
4046
  status: "running"
4047
4047
  }));
4048
4048
  }
4049
+ if (!await store.getRun(id)) host.startChild(workflowName(workflow2), input, id, priority, version);
4049
4050
  throw new WorkflowSuspended(void 0, fanJoin([
4050
4051
  {
4051
4052
  waiterPrefix: `child:${id}`,
@@ -4664,6 +4665,7 @@ function deferred() {
4664
4665
  __name(deferred, "deferred");
4665
4666
  var pendingStateKey = /* @__PURE__ */ __name((at) => String(new Date(at).getTime()), "pendingStateKey");
4666
4667
  var freshPendingKey = /* @__PURE__ */ __name(() => `${Date.now()}-${globalThis.crypto.randomUUID().slice(0, 8)}`, "freshPendingKey");
4668
+ var wakeRequestToken = /* @__PURE__ */ __name((runId) => `durable:wake-after-turn:${runId}`, "wakeRequestToken");
4667
4669
  var CHILD_SIGNAL_PREFIX = "signal:child:";
4668
4670
  var DELIVER_CHUNK = 50;
4669
4671
  function childCompletion(child) {
@@ -7850,6 +7852,7 @@ var WorkflowEngine = class {
7850
7852
  const lockNow = this.clock();
7851
7853
  if (run.lockedBy !== this.instanceId || (run.lockedUntil ?? 0) <= lockNow) {
7852
7854
  if (!await this.store.tryLockRun(run.id, this.instanceId, lockNow + this.leaseMs, lockNow)) {
7855
+ if (!this.leaseRetries.has(run.id)) this.inBackground(this.requestWakeAfterTurn(run.id));
7853
7856
  this.recheckAfterLease(run.id, run.lockedUntil);
7854
7857
  return {
7855
7858
  runId: run.id,
@@ -7863,12 +7866,52 @@ var WorkflowEngine = class {
7863
7866
  if (registered?.singleton && (result.status === "completed" || result.status === "failed" || result.status === "cancelled" || result.status === "dead")) {
7864
7867
  void this.singletons.wakeNext(run).catch(() => void 0);
7865
7868
  }
7869
+ if (result.status === "suspended") this.inBackground(this.honorWakeRequests(run.id));
7870
+ else if (TERMINAL_RUN_STATUSES.includes(result.status)) {
7871
+ this.inBackground(this.takeWakeRequests(run.id));
7872
+ }
7866
7873
  return result;
7867
7874
  } finally {
7868
7875
  held.stop();
7869
7876
  }
7870
7877
  }
7871
7878
  /**
7879
+ * Keep a wake that found `runId`'s lease held by a turn executing elsewhere, durably, so that turn
7880
+ * runs again once it ends instead of suspending on a snapshot that predates what the wake brought
7881
+ * (a signal, a child's completion). The request is a buffered signal on a token no workflow waits
7882
+ * on; the holder takes it after it releases the lease ({@link honorWakeRequests}).
7883
+ *
7884
+ * Written first and looked at second, against a holder that releases first and looks second:
7885
+ * whichever look runs last sees the other side's write. So when the run is no longer executing by
7886
+ * the time this re-reads it, the holder may have looked already, and this side takes the request
7887
+ * and wakes the run itself once the lease is free.
7888
+ */
7889
+ async requestWakeAfterTurn(runId) {
7890
+ await this.store.bufferSignal(wakeRequestToken(runId), null);
7891
+ const run = await this.store.getRun(runId);
7892
+ if (run?.status === "running") return;
7893
+ if (!run) {
7894
+ await this.takeWakeRequests(runId);
7895
+ return;
7896
+ }
7897
+ if (!await this.takeWakeRequests(runId)) return;
7898
+ if (TERMINAL_RUN_STATUSES.includes(run.status)) return;
7899
+ this.recheckAfterLease(runId, run.lockedUntil);
7900
+ }
7901
+ /** After a turn that suspended released its lease: run again if a wake was kept for it. */
7902
+ async honorWakeRequests(runId) {
7903
+ if (!await this.takeWakeRequests(runId)) return;
7904
+ await this.wake(runId).catch((err) => {
7905
+ if (!(err instanceof NamespaceMismatch)) throw err;
7906
+ });
7907
+ }
7908
+ /** Take every wake request kept for `runId`; true when there was one. */
7909
+ async takeWakeRequests(runId) {
7910
+ let taken = false;
7911
+ while (await this.store.takeBufferedSignal(wakeRequestToken(runId))) taken = true;
7912
+ return taken;
7913
+ }
7914
+ /**
7872
7915
  * A wake that found the run leased by another instance is dropped, and normally that is fine: the
7873
7916
  * holder's turn either sees what the wake wrote, or re-checks once it releases the lease
7874
7917
  * ({@link redriveSettledSteps}, {@link redriveSettledChildren}). Not if the holder goes down first