@adonis-agora/durable 0.48.0 → 0.49.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.
Files changed (110) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/assets/spa/assets/index-6nmSTfT7.css +1 -0
  3. package/dist/assets/spa/assets/index-DdB9zxuX.js +16 -0
  4. package/dist/assets/spa/assets/index-DdB9zxuX.js.map +1 -0
  5. package/dist/assets/spa/index.html +2 -2
  6. package/dist/providers/dashboard_provider.d.ts +3 -1
  7. package/dist/providers/dashboard_provider.d.ts.map +1 -1
  8. package/dist/providers/dashboard_provider.js +7 -2
  9. package/dist/providers/dashboard_provider.js.map +1 -1
  10. package/dist/providers/durable_provider.d.ts.map +1 -1
  11. package/dist/providers/durable_provider.js +1 -0
  12. package/dist/providers/durable_provider.js.map +1 -1
  13. package/dist/src/codec-state-store.d.ts +2 -0
  14. package/dist/src/codec-state-store.d.ts.map +1 -1
  15. package/dist/src/codec-state-store.js +6 -0
  16. package/dist/src/codec-state-store.js.map +1 -1
  17. package/dist/src/dashboard/gateway-adapter.d.ts.map +1 -1
  18. package/dist/src/dashboard/gateway-adapter.js +10 -0
  19. package/dist/src/dashboard/gateway-adapter.js.map +1 -1
  20. package/dist/src/dashboard/handlers.d.ts +22 -1
  21. package/dist/src/dashboard/handlers.d.ts.map +1 -1
  22. package/dist/src/dashboard/handlers.js +63 -10
  23. package/dist/src/dashboard/handlers.js.map +1 -1
  24. package/dist/src/dashboard/index.d.ts +1 -1
  25. package/dist/src/dashboard/index.js +1 -1
  26. package/dist/src/dashboard/openapi.d.ts.map +1 -1
  27. package/dist/src/dashboard/openapi.js +26 -2
  28. package/dist/src/dashboard/openapi.js.map +1 -1
  29. package/dist/src/define_config.d.ts +6 -0
  30. package/dist/src/define_config.d.ts.map +1 -1
  31. package/dist/src/define_config.js.map +1 -1
  32. package/dist/src/engine.d.ts +44 -3
  33. package/dist/src/engine.d.ts.map +1 -1
  34. package/dist/src/engine.js +226 -28
  35. package/dist/src/engine.js.map +1 -1
  36. package/dist/src/errors.d.ts +12 -1
  37. package/dist/src/errors.d.ts.map +1 -1
  38. package/dist/src/errors.js +8 -1
  39. package/dist/src/errors.js.map +1 -1
  40. package/dist/src/fan-join.d.ts +32 -3
  41. package/dist/src/fan-join.d.ts.map +1 -1
  42. package/dist/src/fan-join.js +58 -4
  43. package/dist/src/fan-join.js.map +1 -1
  44. package/dist/src/index.d.ts +4 -3
  45. package/dist/src/index.d.ts.map +1 -1
  46. package/dist/src/index.js +3 -2
  47. package/dist/src/index.js.map +1 -1
  48. package/dist/src/interfaces.d.ts +55 -9
  49. package/dist/src/interfaces.d.ts.map +1 -1
  50. package/dist/src/interfaces.js.map +1 -1
  51. package/dist/src/run-detail-summary.d.ts +98 -0
  52. package/dist/src/run-detail-summary.d.ts.map +1 -0
  53. package/dist/src/run-detail-summary.js +150 -0
  54. package/dist/src/run-detail-summary.js.map +1 -0
  55. package/dist/src/run-gateway/interface.d.ts +11 -0
  56. package/dist/src/run-gateway/interface.d.ts.map +1 -1
  57. package/dist/src/run-gateway/proxy-run-gateway.d.ts +3 -0
  58. package/dist/src/run-gateway/proxy-run-gateway.d.ts.map +1 -1
  59. package/dist/src/run-gateway/proxy-run-gateway.js +8 -0
  60. package/dist/src/run-gateway/proxy-run-gateway.js.map +1 -1
  61. package/dist/src/run-gateway/run-request-responder.d.ts +4 -3
  62. package/dist/src/run-gateway/run-request-responder.d.ts.map +1 -1
  63. package/dist/src/run-gateway/run-request-responder.js +23 -3
  64. package/dist/src/run-gateway/run-request-responder.js.map +1 -1
  65. package/dist/src/run-gateway/store-run-gateway.d.ts +3 -0
  66. package/dist/src/run-gateway/store-run-gateway.d.ts.map +1 -1
  67. package/dist/src/run-gateway/store-run-gateway.js +7 -0
  68. package/dist/src/run-gateway/store-run-gateway.js.map +1 -1
  69. package/dist/src/stores/lucid.d.ts +1 -0
  70. package/dist/src/stores/lucid.d.ts.map +1 -1
  71. package/dist/src/stores/lucid.js +16 -3
  72. package/dist/src/stores/lucid.js.map +1 -1
  73. package/dist/src/testing/in-memory-state-store.d.ts +1 -0
  74. package/dist/src/testing/in-memory-state-store.d.ts.map +1 -1
  75. package/dist/src/testing/in-memory-state-store.js +14 -6
  76. package/dist/src/testing/in-memory-state-store.js.map +1 -1
  77. package/dist/src/testing-kit/state-store-conformance.d.ts.map +1 -1
  78. package/dist/src/testing-kit/state-store-conformance.js +59 -0
  79. package/dist/src/testing-kit/state-store-conformance.js.map +1 -1
  80. package/dist/src/transports/bullmq/adaptive-concurrency.d.ts +166 -0
  81. package/dist/src/transports/bullmq/adaptive-concurrency.d.ts.map +1 -0
  82. package/dist/src/transports/bullmq/adaptive-concurrency.js +352 -0
  83. package/dist/src/transports/bullmq/adaptive-concurrency.js.map +1 -0
  84. package/dist/src/transports/bullmq/bullmq-transport.d.ts +18 -1
  85. package/dist/src/transports/bullmq/bullmq-transport.d.ts.map +1 -1
  86. package/dist/src/transports/bullmq/bullmq-transport.js +50 -49
  87. package/dist/src/transports/bullmq/bullmq-transport.js.map +1 -1
  88. package/dist/src/transports/bullmq/deps.d.ts +11 -1
  89. package/dist/src/transports/bullmq/deps.d.ts.map +1 -1
  90. package/dist/src/transports/bullmq/deps.js +12 -1
  91. package/dist/src/transports/bullmq/deps.js.map +1 -1
  92. package/dist/src/transports/bullmq/index.d.ts +1 -0
  93. package/dist/src/transports/bullmq/index.d.ts.map +1 -1
  94. package/dist/src/transports/bullmq/index.js +1 -0
  95. package/dist/src/transports/bullmq/index.js.map +1 -1
  96. package/dist/src/transports/factory.d.ts +21 -2
  97. package/dist/src/transports/factory.d.ts.map +1 -1
  98. package/dist/src/transports/factory.js +5 -1
  99. package/dist/src/transports/factory.js.map +1 -1
  100. package/dist/src/workflow-ctx.d.ts +13 -1
  101. package/dist/src/workflow-ctx.d.ts.map +1 -1
  102. package/dist/src/workflow-ctx.js +25 -1
  103. package/dist/src/workflow-ctx.js.map +1 -1
  104. package/package.json +3 -3
  105. package/skills/durable-cluster/SKILL.md +10 -0
  106. package/skills/durable-transports-stores/SKILL.md +38 -0
  107. package/skills/durable-workflows/SKILL.md +19 -1
  108. package/dist/assets/spa/assets/index-DtGMBUDm.css +0 -1
  109. package/dist/assets/spa/assets/index-_VISH9Xg.js +0 -16
  110. package/dist/assets/spa/assets/index-_VISH9Xg.js.map +0 -1
@@ -0,0 +1,352 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { cpus, totalmem } from 'node:os';
3
+ const DEFAULT_MIN = 1;
4
+ const DEFAULT_MAX = 32;
5
+ const DEFAULT_RAM_CEILING_PCT = 85;
6
+ const DEFAULT_TICK_MS = 2000;
7
+ function clamp(value, min, max) {
8
+ return Math.min(max, Math.max(min, value));
9
+ }
10
+ /** `value` floored when it is a finite number of at least 1, else `fallback`. */
11
+ function wholeAtLeastOne(value, fallback) {
12
+ return value !== undefined && Number.isFinite(value) && value >= 1 ? Math.floor(value) : fallback;
13
+ }
14
+ /** `value` when it is a finite number above 0, else `fallback`. */
15
+ function finitePositive(value, fallback) {
16
+ return value !== undefined && Number.isFinite(value) && value > 0 ? value : fallback;
17
+ }
18
+ /**
19
+ * Normalise a {@link ConcurrencyOption} into a {@link ResolvedConcurrency}: a bare number (or
20
+ * `undefined` → 1) becomes `{ mode:'fixed', fixed }`; `'adaptive'` / `{ mode:'adaptive', ... }`
21
+ * become `{ mode:'adaptive', adaptive }` with every default from the spec applied and `start`
22
+ * clamped into `[min,max]`.
23
+ *
24
+ * A non-finite or out-of-range number falls back to its default (a bad `cpuCeilingPct` turns the
25
+ * CPU ceiling off): BullMQ throws on a `NaN` or sub-1 concurrency, a `NaN` ceiling never brakes, and
26
+ * a `NaN` `tickMs` makes the control loop fire every millisecond.
27
+ */
28
+ export function resolveConcurrency(opt) {
29
+ if (opt === undefined)
30
+ return { mode: 'fixed', fixed: 1 };
31
+ if (typeof opt === 'number')
32
+ return { mode: 'fixed', fixed: wholeAtLeastOne(opt, 1) };
33
+ const overrides = opt === 'adaptive' ? {} : opt;
34
+ const min = wholeAtLeastOne(overrides.min, DEFAULT_MIN);
35
+ const max = Math.max(min, wholeAtLeastOne(overrides.max, DEFAULT_MAX));
36
+ const start = clamp(wholeAtLeastOne(overrides.start, min), min, max);
37
+ const cpuCeilingPct = finitePositive(overrides.cpuCeilingPct, undefined);
38
+ const adaptive = {
39
+ min,
40
+ max,
41
+ start,
42
+ ramCeilingPct: finitePositive(overrides.ramCeilingPct, DEFAULT_RAM_CEILING_PCT),
43
+ tickMs: wholeAtLeastOne(overrides.tickMs, DEFAULT_TICK_MS),
44
+ ...(cpuCeilingPct !== undefined ? { cpuCeilingPct } : {}),
45
+ };
46
+ return { mode: 'adaptive', adaptive };
47
+ }
48
+ // The cgroup "unlimited" sentinel: a limit at/above this is no real cap, so fall through to host total.
49
+ const CGROUP_UNLIMITED = 2 ** 62;
50
+ /**
51
+ * The process memory ceiling in bytes, read once (cgroup limits don't move at runtime): cgroup v2
52
+ * `memory.max`, then cgroup v1 `memory.limit_in_bytes`, then the host total. A `max` / absurdly-large
53
+ * ("unlimited") cgroup value falls through to {@link totalmem}. No new deps — just `node:fs`/`node:os`.
54
+ */
55
+ export function readMemoryLimitBytes() {
56
+ try {
57
+ const raw = readFileSync('/sys/fs/cgroup/memory.max', 'utf8').trim();
58
+ if (raw && raw !== 'max') {
59
+ const n = Number(raw);
60
+ if (Number.isFinite(n) && n > 0 && n < CGROUP_UNLIMITED)
61
+ return n;
62
+ }
63
+ }
64
+ catch {
65
+ /* not cgroup v2 (or unreadable) — try v1 next */
66
+ }
67
+ try {
68
+ const raw = readFileSync('/sys/fs/cgroup/memory/memory.limit_in_bytes', 'utf8').trim();
69
+ const n = Number(raw);
70
+ if (Number.isFinite(n) && n > 0 && n < CGROUP_UNLIMITED)
71
+ return n;
72
+ }
73
+ catch {
74
+ /* not cgroup v1 (or unreadable) — fall through to host total */
75
+ }
76
+ return totalmem();
77
+ }
78
+ // How many recent completions the controller reasons over (latency percentiles + throughput + errors).
79
+ const WINDOW_SIZE = 100;
80
+ // EWMA smoothing for the "no-queuing" baseline (rttLong): low weight so a single fast sample doesn't
81
+ // reset the baseline, but it still tracks a genuine floor shift.
82
+ const RTT_LONG_EWMA_ALPHA = 0.2;
83
+ // Consecutive ticks with in-flight work but zero completions before we call the worker stalled.
84
+ const STALL_TICKS = 2;
85
+ // Throughput is a rate over the trailing minute: a window whose newest completion is older than this
86
+ // reports 0, so an idle worker never advertises its last burst as live.
87
+ const THROUGHPUT_WINDOW_MS = 60_000;
88
+ // The shortest span a rate is computed over, so a worker's first completion doesn't extrapolate a
89
+ // sub-millisecond gap into a huge per-minute figure.
90
+ const THROUGHPUT_MIN_SPAN_MS = 1_000;
91
+ function percentile(sortedAsc, p) {
92
+ if (sortedAsc.length === 0)
93
+ return 0;
94
+ const idx = clamp(Math.ceil((p / 100) * sortedAsc.length) - 1, 0, sortedAsc.length - 1);
95
+ return sortedAsc[idx] ?? 0;
96
+ }
97
+ /**
98
+ * A single gradient-limit (AIMD) concurrency controller, shared by every per-routing-token task
99
+ * worker of one {@link import('./bullmq-transport.js').BullMQTransport}. It tracks in-flight work and a rolling window of
100
+ * completions, and — in `adaptive` mode — re-evaluates the worker's concurrency every `tickMs` from a
101
+ * latency gradient, an error/stall backpressure signal, a hard RAM brake and an optional CPU ceiling.
102
+ *
103
+ * In `fixed` mode no control loop runs and the limit never moves, but the controller still tracks
104
+ * `inFlight`/RSS/throughput/p95 (and samples CPU when a snapshot is taken) so the observability half
105
+ * (the heartbeat status payload) works without adaptive. Either way, {@link snapshot} returns the
106
+ * shared {@link WorkerStatus} contract the heartbeat writer stamps.
107
+ */
108
+ export class AdaptiveController {
109
+ resolved;
110
+ apply;
111
+ now;
112
+ readRss;
113
+ memoryLimitBytes;
114
+ tickMs;
115
+ readCpuUsage;
116
+ cpuCores;
117
+ currentLimit;
118
+ inFlightCount = 0;
119
+ window = [];
120
+ rttLongMs;
121
+ cpuPct;
122
+ lastAdjust;
123
+ completionsThisTick = 0;
124
+ stallTicks = 0;
125
+ lastCpuUsage;
126
+ lastCpuSampleAt = 0;
127
+ timer;
128
+ constructor(options = {}) {
129
+ this.resolved = resolveConcurrency(options.concurrency);
130
+ this.apply = options.apply ?? (() => { });
131
+ this.now = options.now ?? Date.now;
132
+ this.readRss = options.readRss ?? (() => process.memoryUsage().rss);
133
+ this.memoryLimitBytes = (options.readMemoryLimit ?? readMemoryLimitBytes)();
134
+ this.tickMs = this.resolved.adaptive?.tickMs ?? DEFAULT_TICK_MS;
135
+ this.readCpuUsage = options.readCpuUsage ?? (() => process.cpuUsage());
136
+ this.cpuCores = wholeAtLeastOne((options.readCpuCount ?? (() => cpus().length))(), 1);
137
+ this.currentLimit =
138
+ this.resolved.mode === 'adaptive'
139
+ ? (this.resolved.adaptive?.start ?? DEFAULT_MIN)
140
+ : (this.resolved.fixed ?? 1);
141
+ }
142
+ /** The concurrency to hand the BullMQ Worker on construction (fixed N, or the adaptive `start`). */
143
+ get initialLimit() {
144
+ return this.currentLimit;
145
+ }
146
+ /** The live limit in effect right now. */
147
+ get limit() {
148
+ return this.currentLimit;
149
+ }
150
+ /** `true` when the controller will move the limit on its own (adaptive mode). */
151
+ get isAdaptive() {
152
+ return this.resolved.mode === 'adaptive';
153
+ }
154
+ /**
155
+ * Start the control loop (adaptive mode only; fixed mode just takes a CPU baseline). The timer is
156
+ * `unref`'d so it never keeps the process alive; idempotent.
157
+ */
158
+ start() {
159
+ if (this.timer)
160
+ return;
161
+ if (!this.lastCpuUsage)
162
+ this.resetCpuBaseline();
163
+ if (!this.isAdaptive)
164
+ return;
165
+ this.timer = setInterval(() => this.tick(), this.tickMs);
166
+ this.timer.unref?.();
167
+ }
168
+ /** Stop the control loop (call on worker close). Idempotent. */
169
+ stop() {
170
+ if (this.timer) {
171
+ clearInterval(this.timer);
172
+ this.timer = undefined;
173
+ }
174
+ }
175
+ /** A task started — bump in-flight. Call from the job processor before awaiting the body. */
176
+ onStart() {
177
+ this.inFlightCount += 1;
178
+ }
179
+ /**
180
+ * A task settled. ALWAYS drops in-flight and counts toward stall liveness (`completionsThisTick`),
181
+ * so the saturation gate, the heartbeat `inFlight`, and the "alive-but-busy" signal see every task.
182
+ *
183
+ * `kind` gates the MEASUREMENT window: only `'step'` completions push a duration into the rolling
184
+ * window that feeds the latency gradient / throughput / p95 / error rate. A workflow TURN
185
+ * (`kind: 'workflow'`) is NOT measured — a 5ms suspend sitting next to a 2s step would corrupt the
186
+ * gradient — but it still counts as in-flight + forward progress. This is the single concurrency
187
+ * pool measuring steps only; the transport tags the kind when it routes a job by shape.
188
+ */
189
+ onSettle(durationMs, ok, kind) {
190
+ this.inFlightCount = Math.max(0, this.inFlightCount - 1);
191
+ // Forward progress for stall detection counts BOTH kinds — a worker churning workflow turns is
192
+ // not stalled even when no step settles this tick.
193
+ this.completionsThisTick += 1;
194
+ if (kind !== 'step')
195
+ return;
196
+ this.window.push({ durationMs, ok, at: this.now() });
197
+ if (this.window.length > WINDOW_SIZE)
198
+ this.window.shift();
199
+ }
200
+ /**
201
+ * One control-loop iteration: sample CPU, refresh the latency baseline, then decide the next limit
202
+ * and apply it. A no-op in fixed mode. Exposed (not private) so a test can drive ticks
203
+ * deterministically.
204
+ */
205
+ tick() {
206
+ if (this.resolved.mode !== 'adaptive' || !this.resolved.adaptive)
207
+ return;
208
+ this.sampleCpu(this.tickMs);
209
+ this.refreshRttLong();
210
+ // Stall = in-flight work but no completion this tick, sustained over STALL_TICKS.
211
+ if (this.inFlightCount > 0 && this.completionsThisTick === 0)
212
+ this.stallTicks += 1;
213
+ else
214
+ this.stallTicks = 0;
215
+ const stalled = this.stallTicks >= STALL_TICKS;
216
+ this.completionsThisTick = 0;
217
+ this.decide(this.resolved.adaptive, stalled);
218
+ }
219
+ resetCpuBaseline() {
220
+ this.lastCpuUsage = this.readCpuUsage();
221
+ this.lastCpuSampleAt = this.now();
222
+ }
223
+ /** Set `cpuPct` from the CPU time spent since the previous sample, over `elapsedMs` of wall time. */
224
+ sampleCpu(elapsedMs) {
225
+ const previous = this.lastCpuUsage;
226
+ this.resetCpuBaseline();
227
+ if (!previous || !this.lastCpuUsage || elapsedMs <= 0)
228
+ return;
229
+ // CpuUsage is microseconds of CPU; elapsedMs of wall time is elapsedMs*1000 micros per core.
230
+ const micros = this.lastCpuUsage.user - previous.user + (this.lastCpuUsage.system - previous.system);
231
+ const pct = (micros / (elapsedMs * 1000 * this.cpuCores)) * 100;
232
+ if (Number.isFinite(pct))
233
+ this.cpuPct = Math.max(0, pct);
234
+ }
235
+ /** Fixed mode has no control loop, so a snapshot samples CPU itself, at most once per tick period
236
+ * (several heartbeat keys snapshot back to back, and a near-zero span gives a meaningless rate). */
237
+ sampleCpuForSnapshot() {
238
+ if (this.isAdaptive)
239
+ return;
240
+ if (!this.lastCpuUsage) {
241
+ this.resetCpuBaseline();
242
+ return;
243
+ }
244
+ const elapsedMs = this.now() - this.lastCpuSampleAt;
245
+ if (elapsedMs >= this.tickMs)
246
+ this.sampleCpu(elapsedMs);
247
+ }
248
+ refreshRttLong() {
249
+ if (this.window.length === 0)
250
+ return;
251
+ let minDuration = this.window[0]?.durationMs ?? 0;
252
+ for (const c of this.window)
253
+ if (c.durationMs < minDuration)
254
+ minDuration = c.durationMs;
255
+ this.rttLongMs =
256
+ this.rttLongMs === undefined
257
+ ? minDuration
258
+ : RTT_LONG_EWMA_ALPHA * minDuration + (1 - RTT_LONG_EWMA_ALPHA) * this.rttLongMs;
259
+ }
260
+ rssPct() {
261
+ if (this.memoryLimitBytes <= 0)
262
+ return 0;
263
+ return (100 * this.readRss()) / this.memoryLimitBytes;
264
+ }
265
+ decide(cfg, stalled) {
266
+ const current = this.currentLimit;
267
+ const durations = this.window.map((c) => c.durationMs).sort((a, b) => a - b);
268
+ const p50 = percentile(durations, 50);
269
+ const errors = this.window.reduce((n, c) => n + (c.ok ? 0 : 1), 0);
270
+ const errorRate = this.window.length === 0 ? 0 : errors / this.window.length;
271
+ const gradient = this.rttLongMs !== undefined && p50 > 0 ? Math.min(1, this.rttLongMs / p50) : 1;
272
+ const rssPct = this.rssPct();
273
+ const saturated = this.inFlightCount >= current * 0.8;
274
+ let next = current;
275
+ let reason;
276
+ if (rssPct >= cfg.ramCeilingPct) {
277
+ next = Math.max(cfg.min, Math.floor(current * 0.8));
278
+ reason = 'ram_ceiling';
279
+ }
280
+ else if (cfg.cpuCeilingPct !== undefined && (this.cpuPct ?? 0) >= cfg.cpuCeilingPct) {
281
+ // CPU at/over the ceiling: never grow; shed one to relieve it.
282
+ next = Math.max(cfg.min, current - 1);
283
+ reason = 'cpu_ceiling';
284
+ }
285
+ else if (errorRate > 0.2 || stalled) {
286
+ next = Math.max(cfg.min, current - 1);
287
+ reason = 'backpressure';
288
+ }
289
+ else if (gradient < 0.7) {
290
+ next = Math.max(cfg.min, Math.floor(current * gradient));
291
+ reason = 'shrink';
292
+ }
293
+ else if (gradient >= 0.9 && saturated) {
294
+ next = Math.min(cfg.max, current + 1);
295
+ reason = 'grow';
296
+ }
297
+ next = clamp(next, cfg.min, cfg.max);
298
+ if (next === current || reason === undefined)
299
+ return;
300
+ this.currentLimit = next;
301
+ this.lastAdjust = { at: this.now(), from: current, to: next, reason };
302
+ this.apply(next);
303
+ }
304
+ /** The shared {@link WorkerStatus} the heartbeat writer stamps. Best-effort fields are omitted when
305
+ * unmeasured (no completions yet → no p95/throughput; no CPU sample yet → no `cpuPct`). */
306
+ snapshot() {
307
+ this.sampleCpuForSnapshot();
308
+ const durations = this.window.map((c) => c.durationMs).sort((a, b) => a - b);
309
+ const rssBytes = this.readRss();
310
+ const rssLimitBytes = this.memoryLimitBytes;
311
+ const status = {
312
+ runtime: 'node',
313
+ concurrency: {
314
+ mode: this.resolved.mode,
315
+ limit: this.currentLimit,
316
+ ...(this.resolved.mode === 'adaptive' && this.resolved.adaptive
317
+ ? { min: this.resolved.adaptive.min, max: this.resolved.adaptive.max }
318
+ : {}),
319
+ },
320
+ inFlight: this.inFlightCount,
321
+ rssBytes,
322
+ rssLimitBytes,
323
+ rssPct: rssLimitBytes > 0 ? (100 * rssBytes) / rssLimitBytes : 0,
324
+ };
325
+ if (this.cpuPct !== undefined)
326
+ status.cpuPct = this.cpuPct;
327
+ if (this.window.length > 0) {
328
+ status.throughputPerMin = this.throughputPerMin();
329
+ status.p95Ms = percentile(durations, 95);
330
+ }
331
+ if (this.lastAdjust)
332
+ status.lastAdjust = this.lastAdjust;
333
+ return status;
334
+ }
335
+ /**
336
+ * Completions per minute over the trailing minute: the window's completions from the last minute,
337
+ * over the span from the oldest of them (or a full minute, when the window reaches further back) to
338
+ * now, floored at a second. The rate decays while the worker is idle and reads 0 once a minute passes with no completion.
339
+ */
340
+ throughputPerMin() {
341
+ const first = this.window[0];
342
+ if (!first)
343
+ return 0;
344
+ const now = this.now();
345
+ const recent = this.window.filter((c) => now - c.at <= THROUGHPUT_WINDOW_MS).length;
346
+ if (recent === 0)
347
+ return 0;
348
+ const spanMs = clamp(now - first.at, THROUGHPUT_MIN_SPAN_MS, THROUGHPUT_WINDOW_MS);
349
+ return (recent / spanMs) * 60_000;
350
+ }
351
+ }
352
+ //# sourceMappingURL=adaptive-concurrency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adaptive-concurrency.js","sourceRoot":"","sources":["../../../../src/transports/bullmq/adaptive-concurrency.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,8 @@ import type { WorkerDescriptor } from '../../handshake/descriptor.js';
2
2
  import type { GroupHealth, Heartbeat, RemoteTask, RunReply, RunRequest, StartRunMessage, StepResult, TenantEvent, Transport, WorkflowDecision, WorkflowStepEvent, WorkflowTask } from '../../interfaces.js';
3
3
  import { type StepHandler } from '../../protocol.js';
4
4
  import { type WorkflowTurnHandler } from '../../workflow-turn.js';
5
- import type { BullMQDeps, RedisLike } from './deps.js';
5
+ import { type ConcurrencyOption } from './adaptive-concurrency.js';
6
+ import { type BullMQDeps, type RedisLike } from './deps.js';
6
7
  export interface BullMQTransportOptions {
7
8
  /**
8
9
  * Ceiling (ms) `close()` gives the graceful shutdown before it stops waiting and drops the Redis
@@ -34,6 +35,22 @@ export interface BullMQTransportOptions {
34
35
  instanceId?: string | undefined;
35
36
  /** Where a background failure (a failed heartbeat refresh, a result-publish error) is reported. */
36
37
  onError?: ((err: unknown) => void) | undefined;
38
+ /**
39
+ * How many step results this instance settles at once from the results queue. With the default
40
+ * of 1, a wide fan's results queue up behind one checkpoint write at a time on each pod. The engine
41
+ * still resumes one run's turns in order on an instance, so a run never gets two concurrent turns
42
+ * from it. A non-finite or sub-1 value falls back to the default. Default 1.
43
+ */
44
+ resultConcurrency?: number | undefined;
45
+ /**
46
+ * How many tasks each per-routing-token task worker runs at once. A number pins it (a non-finite or
47
+ * sub-1 value falls back to 1). `'adaptive'` or `{ mode: 'adaptive', min, max, start, tickMs,
48
+ * ramCeilingPct, cpuCeilingPct }` lets an {@link AdaptiveController} move the limit at runtime from
49
+ * step latency, error rate and stalls, braking on RAM (and CPU, when a ceiling is set). The live
50
+ * limit, in-flight count and last adjustment ride the worker heartbeat in both modes. Unset leaves
51
+ * each task worker at the deps' default concurrency.
52
+ */
53
+ concurrency?: ConcurrencyOption | undefined;
37
54
  }
38
55
  /**
39
56
  * A queue-backed {@link Transport} over BullMQ/Redis, BYTE-COMPATIBLE with the aviary
@@ -1 +1 @@
1
- {"version":3,"file":"bullmq-transport.d.ts","sourceRoot":"","sources":["../../../../src/transports/bullmq/bullmq-transport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bullmq-transport.d.ts","sourceRoot":"","sources":["../../../../src/transports/bullmq/bullmq-transport.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,8 @@
1
1
  import { runStepHandler } from '../../protocol.js';
2
2
  import { swallowSubscribe } from '../../safe-subscribe.js';
3
3
  import { isWorkflowTask } from '../../workflow-turn.js';
4
+ import { AdaptiveController } from './adaptive-concurrency.js';
5
+ import { workerConcurrency, } from './deps.js';
4
6
  import { decisionsName, effectivePrefix, heartbeatChannel, resultsName, routingToken, runReplyChannel, runRequestName, startRunName, stepEventsName, tasksName, tenantEventsChannel, workerDescriptorTokenPrefix, workerHeartbeatKey, workerHeartbeatKeyPrefix, } from './naming.js';
5
7
  import { buildInstanceId, heartbeatKeyValue, jobOptions, parseHeartbeatValue, resultJobOptions, taskJobOptions, WORKER_HEARTBEAT_INTERVAL_MS, WORKER_HEARTBEAT_TTL_SECONDS, } from './serialization.js';
6
8
  /**
@@ -30,6 +32,11 @@ export class BullMQTransport {
30
32
  #hasOnError;
31
33
  #closing = false;
32
34
  #closeTimeoutMs;
35
+ #resultConcurrency;
36
+ #concurrency;
37
+ // Shared by every task worker: tracks in-flight work and the step-completion window, snapshots the
38
+ // heartbeat status and, in adaptive mode, sets every task worker's `concurrency` on each move.
39
+ #controller;
33
40
  #handlers = new Map();
34
41
  // Worker-side workflow turn consumers, keyed by workflow name — a `workflow`-shaped job routes here.
35
42
  #workflowTurns = new Map();
@@ -46,11 +53,6 @@ export class BullMQTransport {
46
53
  #workerRedis;
47
54
  #workerHeartbeatTimer;
48
55
  #heartbeatTokens = new Set();
49
- // Live execution telemetry carried on the heartbeat (the console's worker cards): jobs currently
50
- // in a handler, and a bounded window of recent completions for throughput / p95.
51
- #inFlightJobs = 0;
52
- #recentCompletions = [];
53
- #lastCpu;
54
56
  // P4 store-less protocol: start-run / run-request queues + consumers, run-reply / tenant-events pub/sub.
55
57
  #startRunWorker;
56
58
  #runRequestWorker;
@@ -72,6 +74,21 @@ export class BullMQTransport {
72
74
  this.#onError = options.onError ?? (() => { });
73
75
  this.#hasOnError = options.onError !== undefined;
74
76
  this.#closeTimeoutMs = options.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS;
77
+ this.#resultConcurrency = workerConcurrency(options.resultConcurrency);
78
+ this.#concurrency = options.concurrency;
79
+ }
80
+ /** The shared concurrency controller, created on first use (a task worker, a job or a heartbeat). */
81
+ #concurrencyController() {
82
+ if (!this.#controller) {
83
+ this.#controller = new AdaptiveController({
84
+ ...(this.#concurrency !== undefined ? { concurrency: this.#concurrency } : {}),
85
+ apply: (limit) => {
86
+ for (const worker of this.#taskWorkers.values())
87
+ worker.concurrency = limit;
88
+ },
89
+ });
90
+ }
91
+ return this.#controller;
75
92
  }
76
93
  /** Stable id stamped on this instance's worker-liveness keys. */
77
94
  get instanceId() {
@@ -158,7 +175,10 @@ export class BullMQTransport {
158
175
  #ensureTaskWorker(token) {
159
176
  if (this.#taskWorkers.has(token))
160
177
  return;
161
- const worker = this.#deps.makeWorker(tasksName(this.#effectivePrefix(), token), (job) => this.#runJob(job.data));
178
+ const controller = this.#concurrencyController();
179
+ // A new worker starts at the controller's live limit, so a token registered after an adaptive
180
+ // move joins at the current limit rather than at `start`.
181
+ const worker = this.#deps.makeWorker(tasksName(this.#effectivePrefix(), token), (job) => this.#runJob(job.data), this.#concurrency !== undefined ? { concurrency: controller.limit } : undefined);
162
182
  // Terminal-failure bridge: a task job reaching a TERMINAL `failed` state is an INFRASTRUCTURE
163
183
  // failure (worker crash / stalled-count exhaustion), never a handler business error —
164
184
  // `runStepHandler` catches every handler throw and publishes a failed StepResult so the job
@@ -174,23 +194,31 @@ export class BullMQTransport {
174
194
  void this.#bridgeTaskFailure(identity, job?.failedReason ?? error?.message ?? 'unknown');
175
195
  });
176
196
  this.#taskWorkers.set(token, worker);
197
+ controller.start();
177
198
  this.#startWorkerHeartbeat(token);
178
199
  }
179
200
  /** Route one task job by SHAPE (spec §6.3): a workflow turn runs the replay → decision path, a step
180
201
  * runs the `runStepHandler` → result path. Both kinds share a token's queue but never each other's. */
181
202
  async #runJob(data) {
203
+ const controller = this.#concurrencyController();
182
204
  const startedAt = Date.now();
183
- this.#inFlightJobs += 1;
184
- try {
185
- if (isWorkflowTask(data))
205
+ controller.onStart();
206
+ // Only step durations feed the latency gradient; a workflow turn still counts as in-flight work
207
+ // and as forward progress for stall detection.
208
+ if (isWorkflowTask(data)) {
209
+ try {
186
210
  return await this.#runWorkflowTask(data);
187
- return await this.#runTask(data);
211
+ }
212
+ finally {
213
+ controller.onSettle(Date.now() - startedAt, true, 'workflow');
214
+ }
215
+ }
216
+ let ok = false;
217
+ try {
218
+ ok = await this.#runTask(data);
188
219
  }
189
220
  finally {
190
- this.#inFlightJobs -= 1;
191
- this.#recentCompletions.push({ at: Date.now(), ms: Date.now() - startedAt });
192
- if (this.#recentCompletions.length > 200)
193
- this.#recentCompletions.shift();
221
+ controller.onSettle(Date.now() - startedAt, ok, 'step');
194
222
  }
195
223
  }
196
224
  /** Worker side: replay a workflow turn and publish its {@link WorkflowDecision} on `${P}-decisions`
@@ -235,6 +263,7 @@ export class BullMQTransport {
235
263
  throw err;
236
264
  }
237
265
  }
266
+ /** Run one step and publish its result. Resolves `true` when the step completed. */
238
267
  async #runTask(task) {
239
268
  let result;
240
269
  try {
@@ -257,6 +286,7 @@ export class BullMQTransport {
257
286
  }
258
287
  try {
259
288
  await this.#queue(resultsName(this.#effectivePrefix())).add('result', result, resultJobOptions());
289
+ return result.status === 'completed';
260
290
  }
261
291
  catch (err) {
262
292
  // The results-queue publish itself threw (e.g. Redis blipped). We still hold `task`, so
@@ -296,7 +326,7 @@ export class BullMQTransport {
296
326
  onResult(handler) {
297
327
  if (this.#resultsWorker)
298
328
  return;
299
- this.#resultsWorker = this.#deps.makeWorker(resultsName(this.#effectivePrefix()), (job) => handler(job.data));
329
+ this.#resultsWorker = this.#deps.makeWorker(resultsName(this.#effectivePrefix()), (job) => handler(job.data), { concurrency: this.#resultConcurrency });
300
330
  this.#resultsWorker.on('failed', (job, err) => {
301
331
  if (!job || (job.attemptsMade ?? 0) < (job.opts?.attempts ?? 1))
302
332
  return;
@@ -342,42 +372,12 @@ export class BullMQTransport {
342
372
  // worker liveness registry — `${P}-worker-heartbeat:${token}:${instanceId}` (SET … EX 35)
343
373
  // ---------------------------------------------------------------------------
344
374
  /**
345
- * The live execution/resource snapshot each heartbeat carries — what lights up the console's
346
- * worker cards (concurrency mode, in-flight, RSS/CPU, throughput, p95). Parity with the NestJS
347
- * fleet's heartbeat payload; the AdonisJS heartbeat used to carry only the timestamp, leaving the
348
- * cards permanently on "no status". Cheap by construction: process-level reads plus a bounded
349
- * completions window.
375
+ * The live execution/resource snapshot each heartbeat carries, which lights up the console's worker
376
+ * cards (concurrency mode and live limit, in-flight, RSS/CPU, throughput, p95, last adjustment):
377
+ * the same {@link WorkerStatus} the NestJS fleet stamps.
350
378
  */
351
379
  #workerStatus() {
352
- const now = Date.now();
353
- const windowStart = now - 60_000;
354
- const recent = this.#recentCompletions.filter((c) => c.at >= windowStart);
355
- const durations = [...this.#recentCompletions].map((c) => c.ms).sort((a, b) => a - b);
356
- const p95 = durations.length
357
- ? durations[Math.min(durations.length - 1, Math.floor(durations.length * 0.95))]
358
- : undefined;
359
- const mem = process.memoryUsage();
360
- // CPU% since the previous beat (user+system µs over wall-clock µs).
361
- let cpuPct;
362
- const cpuNow = process.cpuUsage();
363
- if (this.#lastCpu) {
364
- const elapsedUs = (now - this.#lastCpu.at) * 1000;
365
- const usedUs = cpuNow.user - this.#lastCpu.usage.user + (cpuNow.system - this.#lastCpu.usage.system);
366
- if (elapsedUs > 0)
367
- cpuPct = Math.round((usedUs / elapsedUs) * 100);
368
- }
369
- this.#lastCpu = { usage: cpuNow, at: now };
370
- return {
371
- runtime: 'node',
372
- // One BullMQ Worker per handled routing token, each at the adapter's default concurrency (1)
373
- // — so this instance can run up to one job per token simultaneously.
374
- concurrency: { mode: 'fixed', limit: Math.max(1, this.#taskWorkers.size) },
375
- inFlight: this.#inFlightJobs,
376
- rssBytes: mem.rss,
377
- ...(cpuPct !== undefined ? { cpuPct } : {}),
378
- throughputPerMin: recent.length,
379
- ...(p95 !== undefined ? { p95Ms: p95 } : {}),
380
- };
380
+ return this.#concurrencyController().snapshot();
381
381
  }
382
382
  /** Refresh EVERY handled routing token's TTL'd liveness key on ONE shared 10s interval until
383
383
  * `close()`. A newly added token beats immediately so a freshly-registered handler is visible
@@ -630,6 +630,7 @@ export class BullMQTransport {
630
630
  /** Close all workers/queues and disconnect pub/sub + heartbeat connections so the process can exit. */
631
631
  async close() {
632
632
  this.#closing = true;
633
+ this.#controller?.stop();
633
634
  if (this.#workerHeartbeatTimer)
634
635
  clearInterval(this.#workerHeartbeatTimer);
635
636
  this.#workerHeartbeatTimer = undefined;