@adonis-agora/durable 0.39.1 → 0.41.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,73 @@
1
1
  # @adonis-agora/durable
2
2
 
3
+ ## 0.41.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#198](https://github.com/DavideCarvalho/adonis-agora-durable/pull/198) [`2b74239`](https://github.com/DavideCarvalho/adonis-agora-durable/commit/2b74239efc6defb62d7f978d8df060384791447a) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Re-drive a `gather_calls` step whose worker was killed mid-step, instead of orphaning it forever.
8
+
9
+ A polyglot workflow's turn declares its fan-out as `call` commands; `applyCommands` writes a
10
+ `pending` checkpoint and dispatches each one. On every later turn the worker's replay RE-EMITS the
11
+ calls it is still waiting on, and the engine skips a command whose checkpoint already exists — the
12
+ guard that stops a partially-settled fan-out from double-dispatching its live siblings.
13
+
14
+ That guard had no notion of a LOST job. A worker killed mid-step (an OOM kill) takes its in-flight
15
+ job with it, and the transport cannot always put it back: the `bullmq` transport's terminal-failure
16
+ bridge (`worker.on('failed')`) only exists inside a JS consumer, so a first-class Python fleet (see
17
+ `docs/python.mdx`) has none; the `queue` transport's stalled-claim sweep gives up past
18
+ `maxStalledCount`, and never runs at all on an adapter without `recoverStalledJobs`. Nothing then
19
+ publishes a `StepResult`, so the checkpoint stayed `pending` — "out for delivery" — and every turn
20
+ skipped it: the run woke on the `reconcileMs` sweep, dispatched a turn, had the calls re-emitted,
21
+ skipped them, and slept. Forever, with `attempts` never leaving 1 and the checkpoint's `wakeAt` NULL.
22
+
23
+ Those two tells named the gap exactly. `callRemote` (the `ctx.step` path) stamps a re-dispatch
24
+ deadline on the pending checkpoint's `wakeAt` and honours `remoteRedispatchMs`; the `call` path did
25
+ neither — so the self-heal `docs/reliability/failure-modes.mdx` documents as acting "on the
26
+ checkpoint regardless of which transport lost the job" **did not exist for a fan-out**, no matter how
27
+ it was configured.
28
+
29
+ Now the two paths share one policy, and a pending remote checkpoint's `wakeAt` means the same thing
30
+ in both: the step's LEASE.
31
+
32
+ - A settled checkpoint always wins — a step that completed just before the crash is never re-run.
33
+ - `remoteRedispatchMs` unset (still the default) keeps the by-design "re-suspend, never
34
+ re-dispatch": a merely-slow worker is never double-run.
35
+ - Set it and the dispatched step carries a lease, and the run suspends ON it (never later than the
36
+ `reconcileMs` sweep would have woken it anyway). Only a LAPSED lease re-dispatches, bounded by
37
+ `remoteRedispatchMax` (default 10); past the bound the step is failed `remote_step_lost`, which
38
+ enters the run's history so the workflow's own error path surfaces it rather than the engine
39
+ looping.
40
+ - A step-scoped heartbeat now RENEWS that lease durably, so a worker still holding a long step keeps
41
+ it — the in-memory rearm only ever protected a `timeoutMs` step, and only on the instance that
42
+ dispatched it. The renewal is throttled by the lease itself (a write only once the window is half
43
+ spent), so a worker beating every few seconds costs one read + one write per half
44
+ `remoteRedispatchMs`, not one per beat — the same concern that throttles the liveness write in
45
+ `persistHeartbeat`. And it is best-effort end to end: the renewal runs on the transport's SERIAL
46
+ heartbeat handler, so no read or write of it can reject into that loop — a store hiccup that took the
47
+ beat loop down would lapse every lease at once.
48
+ - Observability: a re-drive emits `step.started` with `redispatched: true` and appends a `warn`
49
+ `step.redispatched` event to the checkpoint's own trail (`step.lost` at the bound), so "re-driven
50
+ after a lost worker" reads differently from a failure retry — in the dashboard and in the database.
51
+
52
+ No schema change and no new config knob: `remoteRedispatchMs` / `remoteRedispatchMax` (already
53
+ carried through from `config/durable.ts`) now simply mean what they say for a fan-out too.
54
+
55
+ ## 0.40.0
56
+
57
+ ### Minor Changes
58
+
59
+ - [`b62ce42`](https://github.com/DavideCarvalho/adonis-agora-durable/commit/b62ce428e6262cf78f55ac96ef1433a3197c1878) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Require cron-parser 5
60
+
61
+ The optional `cron-parser` peer narrows from `^4.0.0 || ^5.0.0` to `^5.0.0`, and the
62
+ scheduler no longer recognises v4's `parseExpression` entry point — an unsupported major
63
+ now fails at load with a message naming the version it wants, rather than resolving to a
64
+ parser this code no longer drives correctly.
65
+
66
+ Only projects that schedule workflows by cron are affected, and only those still on
67
+ cron-parser 4: install cron-parser 5. Nothing about cron expressions, timezones or fire
68
+ times changes; the scheduler computed identical fires under both majors, which is what
69
+ the compatibility suite asserted before this.
70
+
3
71
  ## 0.39.1
4
72
 
5
73
  ### Patch Changes