@dudousxd/nestjs-durable-core 0.74.0 → 0.75.1

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
@@ -2350,12 +2350,14 @@ var ScheduleClient = class {
2350
2350
  const store = this.requireStore();
2351
2351
  const started = [];
2352
2352
  for (let round = 0; round < 50; round++) {
2353
+ if (this.host.isDraining?.()) break;
2353
2354
  const due = await store.listSchedules({
2354
2355
  dueBy: nowMs,
2355
2356
  namespace: this.options.namespace,
2356
2357
  limit: TICK_BATCH
2357
2358
  });
2358
2359
  for (const record of due) {
2360
+ if (this.host.isDraining?.()) return started;
2359
2361
  const runId = await this.fire(record, nowMs);
2360
2362
  if (runId) started.push(runId);
2361
2363
  }
@@ -3868,6 +3870,10 @@ var WorkflowEngine = class {
3868
3870
  /** Executions currently in flight, so a graceful shutdown can wait for them to settle. */
3869
3871
  inflight = /* @__PURE__ */ new Set();
3870
3872
  draining = false;
3873
+ /** Wait already spent on this shutdown before `drain()` (see `beginDrain`). */
3874
+ drainSpentMs = 0;
3875
+ /** Re-arms the deadline of every `drain()` currently waiting, when more wait is reported. */
3876
+ drainRearms = /* @__PURE__ */ new Set();
3871
3877
  constructor(deps) {
3872
3878
  this.store = deps.store;
3873
3879
  this.pool = new TransportPool(deps.transports ?? (deps.transport ? [
@@ -3916,7 +3922,8 @@ var WorkflowEngine = class {
3916
3922
  });
3917
3923
  this.schedules = new ScheduleClient(this.store, {
3918
3924
  start: /* @__PURE__ */ __name((workflow, input, runId, opts) => this.start(workflow, input, runId, opts), "start"),
3919
- getRun: /* @__PURE__ */ __name((runId) => this.store.getRun(runId), "getRun")
3925
+ getRun: /* @__PURE__ */ __name((runId) => this.store.getRun(runId), "getRun"),
3926
+ isDraining: /* @__PURE__ */ __name(() => this.draining, "isDraining")
3920
3927
  }, {
3921
3928
  namespace: this.namespace,
3922
3929
  clock: this.clock
@@ -4541,23 +4548,59 @@ var WorkflowEngine = class {
4541
4548
  return p;
4542
4549
  }
4543
4550
  /**
4551
+ * Stop picking up new work without waiting: recovery, timer, schedule and timeout sweeps (and
4552
+ * their per-run lock loops) start nothing once this is called, and {@link runOne} returns null. A
4553
+ * poller calls it on its own shutdown so an in-flight sweep stops locking runs before it is awaited.
4554
+ *
4555
+ * `spentMs` is wait time ALREADY spent on this shutdown (the poller awaiting its in-flight sweep).
4556
+ * It is deducted from the budget of a later {@link drain}, so that wait plus the drain stay within
4557
+ * one `shutdownTimeoutMs`. Only time actually spent waiting is charged: time that merely passes
4558
+ * between this call and `drain()` (other hooks, the HTTP server closing) never is. Calls add up.
4559
+ */
4560
+ beginDrain(spentMs = 0) {
4561
+ this.draining = true;
4562
+ if (Number.isFinite(spentMs) && spentMs > 0) {
4563
+ this.drainSpentMs += spentMs;
4564
+ for (const rearm of this.drainRearms) rearm();
4565
+ }
4566
+ }
4567
+ /**
4544
4568
  * Graceful shutdown: stop picking up new runs (recovery/timer become no-ops) and wait for
4545
4569
  * in-flight executions to settle, up to `timeoutMs`. Call from your app's shutdown hook so a
4546
4570
  * deploy hands off cleanly instead of leaving runs to the lease timeout.
4571
+ *
4572
+ * `timeoutMs` is the total budget for waiting on in-flight runs; any wait already reported through
4573
+ * {@link beginDrain} (`spentMs`) is deducted, so a shutdown that already waited on the timer poll
4574
+ * for part of it waits only for the remainder. With nothing reported, the full `timeoutMs` applies.
4547
4575
  */
4548
4576
  async drain(timeoutMs = 1e4) {
4549
- this.draining = true;
4577
+ this.beginDrain();
4550
4578
  if (this.inflight.size === 0) return;
4579
+ const start = Date.now();
4580
+ let rearm = /* @__PURE__ */ __name(() => {
4581
+ }, "rearm");
4582
+ let timeout;
4551
4583
  const timer = new Promise((resolve) => {
4552
- const t = setTimeout(resolve, timeoutMs);
4553
- t.unref?.();
4584
+ rearm = /* @__PURE__ */ __name(() => {
4585
+ clearTimeout(timeout);
4586
+ const remaining = Math.max(0, start + timeoutMs - this.drainSpentMs - Date.now());
4587
+ timeout = setTimeout(resolve, remaining);
4588
+ timeout.unref?.();
4589
+ }, "rearm");
4590
+ rearm();
4554
4591
  });
4555
- await Promise.race([
4556
- Promise.allSettled([
4557
- ...this.inflight
4558
- ]),
4559
- timer
4560
- ]);
4592
+ this.drainRearms.add(rearm);
4593
+ try {
4594
+ await Promise.race([
4595
+ Promise.allSettled([
4596
+ ...this.inflight
4597
+ ]),
4598
+ timer
4599
+ ]);
4600
+ } finally {
4601
+ this.drainRearms.delete(rearm);
4602
+ clearTimeout(timeout);
4603
+ }
4561
4604
  }
4562
4605
  /**
4563
4606
  * Cancel in-flight runs that have outlived their workflow's `executionTimeout`. Call it from the
@@ -4591,6 +4634,7 @@ var WorkflowEngine = class {
4591
4634
  */
4592
4635
  async sweepTimeouts(now = this.clock()) {
4593
4636
  for (const reg of new Set(this.latest.values())) {
4637
+ if (this.draining) return;
4594
4638
  if (reg.executionTimeoutMs == null) continue;
4595
4639
  const deadline = now - reg.executionTimeoutMs;
4596
4640
  const inflight = [
@@ -4606,6 +4650,7 @@ var WorkflowEngine = class {
4606
4650
  })
4607
4651
  ];
4608
4652
  for (const run of inflight) {
4653
+ if (this.draining) return;
4609
4654
  if (run.createdAt.getTime() > deadline) continue;
4610
4655
  const error2 = {
4611
4656
  message: "execution timeout",
@@ -4635,6 +4680,7 @@ var WorkflowEngine = class {
4635
4680
  if (this.draining) return [];
4636
4681
  const results = [];
4637
4682
  for (const listed of await this.store.listIncompleteRuns(this.namespace)) {
4683
+ if (this.draining) break;
4638
4684
  const acquired = await this.store.tryLockRun(listed.id, this.instanceId, nowMs + this.leaseMs, nowMs);
4639
4685
  if (!acquired) continue;
4640
4686
  const run = await this.store.getRun(listed.id);
@@ -5070,8 +5116,13 @@ var WorkflowEngine = class {
5070
5116
  if (this.draining) return [];
5071
5117
  const results = [];
5072
5118
  for (const run of runs) {
5119
+ if (this.draining) break;
5073
5120
  const acquired = await this.store.tryLockRun(run.id, this.instanceId, nowMs + this.leaseMs, nowMs);
5074
5121
  if (!acquired) continue;
5122
+ if (this.draining) {
5123
+ await this.store.releaseRunLock(run.id);
5124
+ break;
5125
+ }
5075
5126
  try {
5076
5127
  const settled = onLocked ? await onLocked(run) : void 0;
5077
5128
  results.push(settled ?? await this.resumeImplicit(run.id));