@blokjs/runner 0.4.0 → 0.6.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/dist/Blok.js +32 -3
- package/dist/Blok.js.map +1 -1
- package/dist/Configuration.d.ts +41 -5
- package/dist/Configuration.js +215 -92
- package/dist/Configuration.js.map +1 -1
- package/dist/ForEachNode.d.ts +59 -0
- package/dist/ForEachNode.js +522 -0
- package/dist/ForEachNode.js.map +1 -0
- package/dist/LoopMaxIterationsError.d.ts +11 -0
- package/dist/LoopMaxIterationsError.js +18 -0
- package/dist/LoopMaxIterationsError.js.map +1 -0
- package/dist/LoopNode.d.ts +36 -0
- package/dist/LoopNode.js +182 -0
- package/dist/LoopNode.js.map +1 -0
- package/dist/Runner.d.ts +11 -1
- package/dist/Runner.js +9 -2
- package/dist/Runner.js.map +1 -1
- package/dist/RunnerSteps.js +419 -112
- package/dist/RunnerSteps.js.map +1 -1
- package/dist/RuntimeAdapterNode.d.ts +2 -1
- package/dist/RuntimeAdapterNode.js +2 -2
- package/dist/RuntimeAdapterNode.js.map +1 -1
- package/dist/RuntimeRegistry.d.ts +23 -2
- package/dist/RuntimeRegistry.js +31 -2
- package/dist/RuntimeRegistry.js.map +1 -1
- package/dist/SubworkflowNode.d.ts +106 -0
- package/dist/SubworkflowNode.js +261 -3
- package/dist/SubworkflowNode.js.map +1 -1
- package/dist/SwitchNode.d.ts +37 -0
- package/dist/SwitchNode.js +153 -0
- package/dist/SwitchNode.js.map +1 -0
- package/dist/TriggerBase.d.ts +50 -0
- package/dist/TriggerBase.js +262 -4
- package/dist/TriggerBase.js.map +1 -1
- package/dist/TryCatchNode.d.ts +32 -0
- package/dist/TryCatchNode.js +207 -0
- package/dist/TryCatchNode.js.map +1 -0
- package/dist/adapters/grpc/GrpcCodec.js +2 -2
- package/dist/adapters/grpc/GrpcRuntimeAdapter.d.ts +6 -4
- package/dist/adapters/grpc/GrpcRuntimeAdapter.js +6 -4
- package/dist/adapters/grpc/GrpcRuntimeAdapter.js.map +1 -1
- package/dist/adapters/grpc/types.d.ts +7 -5
- package/dist/adapters/grpc/types.js.map +1 -1
- package/dist/adapters/transport.d.ts +12 -41
- package/dist/adapters/transport.js +21 -70
- package/dist/adapters/transport.js.map +1 -1
- package/dist/cache/NodeResultCache.js +7 -0
- package/dist/cache/NodeResultCache.js.map +1 -1
- package/dist/concurrency/NatsKvConcurrencyBackend.js +18 -5
- package/dist/concurrency/NatsKvConcurrencyBackend.js.map +1 -1
- package/dist/concurrency/RedisConcurrencyBackend.d.ts +64 -0
- package/dist/concurrency/RedisConcurrencyBackend.js +374 -0
- package/dist/concurrency/RedisConcurrencyBackend.js.map +1 -0
- package/dist/concurrency/createConcurrencyBackend.d.ts +1 -0
- package/dist/concurrency/createConcurrencyBackend.js +5 -1
- package/dist/concurrency/createConcurrencyBackend.js.map +1 -1
- package/dist/defineNode.d.ts +8 -0
- package/dist/defineNode.js +25 -5
- package/dist/defineNode.js.map +1 -1
- package/dist/graphql/GraphQLSchemaGenerator.js +1 -1
- package/dist/graphql/GraphQLSchemaGenerator.js.map +1 -1
- package/dist/index.d.ts +10 -6
- package/dist/index.js +13 -9
- package/dist/index.js.map +1 -1
- package/dist/marketplace/RuntimeCatalog.d.ts +6 -0
- package/dist/marketplace/RuntimeCatalog.js.map +1 -1
- package/dist/marketplace/RuntimeDiscovery.d.ts +2 -2
- package/dist/marketplace/RuntimeDiscovery.js +18 -6
- package/dist/marketplace/RuntimeDiscovery.js.map +1 -1
- package/dist/monitoring/ConcurrencyMetrics.d.ts +26 -0
- package/dist/monitoring/ConcurrencyMetrics.js +36 -4
- package/dist/monitoring/ConcurrencyMetrics.js.map +1 -1
- package/dist/monitoring/ForEachWaitMetrics.d.ts +22 -0
- package/dist/monitoring/ForEachWaitMetrics.js +36 -0
- package/dist/monitoring/ForEachWaitMetrics.js.map +1 -0
- package/dist/openapi/OpenAPIGenerator.js +7 -2
- package/dist/openapi/OpenAPIGenerator.js.map +1 -1
- package/dist/runtime/PrimitiveStack.d.ts +64 -0
- package/dist/runtime/PrimitiveStack.js +92 -0
- package/dist/runtime/PrimitiveStack.js.map +1 -0
- package/dist/scheduling/DebounceBackend.d.ts +108 -0
- package/dist/scheduling/DebounceBackend.js +23 -0
- package/dist/scheduling/DebounceBackend.js.map +1 -0
- package/dist/scheduling/DebounceCoordinator.d.ts +65 -12
- package/dist/scheduling/DebounceCoordinator.js +234 -13
- package/dist/scheduling/DebounceCoordinator.js.map +1 -1
- package/dist/scheduling/DeferredRunScheduler.d.ts +28 -0
- package/dist/scheduling/DeferredRunScheduler.js +105 -3
- package/dist/scheduling/DeferredRunScheduler.js.map +1 -1
- package/dist/scheduling/NatsKvDebounceBackend.d.ts +53 -0
- package/dist/scheduling/NatsKvDebounceBackend.js +334 -0
- package/dist/scheduling/NatsKvDebounceBackend.js.map +1 -0
- package/dist/scheduling/RedisDebounceBackend.d.ts +49 -0
- package/dist/scheduling/RedisDebounceBackend.js +356 -0
- package/dist/scheduling/RedisDebounceBackend.js.map +1 -0
- package/dist/scheduling/createDebounceBackend.d.ts +25 -0
- package/dist/scheduling/createDebounceBackend.js +39 -0
- package/dist/scheduling/createDebounceBackend.js.map +1 -0
- package/dist/security/AuditLogger.js +1 -1
- package/dist/security/AuditLogger.js.map +1 -1
- package/dist/security/AuthMiddleware.d.ts +19 -20
- package/dist/security/AuthMiddleware.js +35 -20
- package/dist/security/AuthMiddleware.js.map +1 -1
- package/dist/security/OAuthProvider.js +2 -2
- package/dist/security/OAuthProvider.js.map +1 -1
- package/dist/security/SecretManager.js +14 -13
- package/dist/security/SecretManager.js.map +1 -1
- package/dist/security/index.d.ts +3 -1
- package/dist/security/index.js +3 -1
- package/dist/security/index.js.map +1 -1
- package/dist/testing/TestHarness.d.ts +27 -12
- package/dist/testing/TestHarness.js +19 -3
- package/dist/testing/TestHarness.js.map +1 -1
- package/dist/testing/WorkflowTestRunner.js +0 -7
- package/dist/testing/WorkflowTestRunner.js.map +1 -1
- package/dist/tracing/InMemoryRunStore.d.ts +14 -1
- package/dist/tracing/InMemoryRunStore.js +95 -6
- package/dist/tracing/InMemoryRunStore.js.map +1 -1
- package/dist/tracing/PostgresRunStore.d.ts +28 -2
- package/dist/tracing/PostgresRunStore.js +276 -3
- package/dist/tracing/PostgresRunStore.js.map +1 -1
- package/dist/tracing/RoutingDiagnostics.d.ts +55 -0
- package/dist/tracing/RoutingDiagnostics.js +50 -0
- package/dist/tracing/RoutingDiagnostics.js.map +1 -0
- package/dist/tracing/RunStore.d.ts +82 -1
- package/dist/tracing/RunTracker.d.ts +7 -1
- package/dist/tracing/RunTracker.js +23 -0
- package/dist/tracing/RunTracker.js.map +1 -1
- package/dist/tracing/SqliteRunStore.d.ts +57 -2
- package/dist/tracing/SqliteRunStore.js +408 -48
- package/dist/tracing/SqliteRunStore.js.map +1 -1
- package/dist/tracing/TraceRouter.js +380 -18
- package/dist/tracing/TraceRouter.js.map +1 -1
- package/dist/tracing/createStore.js +14 -3
- package/dist/tracing/createStore.js.map +1 -1
- package/dist/tracing/metadataFilter.d.ts +63 -0
- package/dist/tracing/metadataFilter.js +224 -0
- package/dist/tracing/metadataFilter.js.map +1 -0
- package/dist/tracing/types.d.ts +331 -7
- package/dist/utils/envAllowlist.d.ts +35 -0
- package/dist/utils/envAllowlist.js +113 -0
- package/dist/utils/envAllowlist.js.map +1 -0
- package/dist/version/RuntimeVersionValidator.d.ts +38 -0
- package/dist/version/RuntimeVersionValidator.js +121 -0
- package/dist/version/RuntimeVersionValidator.js.map +1 -0
- package/dist/visualization/WorkflowVisualizer.js +4 -4
- package/dist/visualization/WorkflowVisualizer.js.map +1 -1
- package/dist/workflow/PersistenceHelper.d.ts +18 -10
- package/dist/workflow/PersistenceHelper.js +35 -9
- package/dist/workflow/PersistenceHelper.js.map +1 -1
- package/dist/workflow/WorkflowNormalizer.d.ts +19 -1
- package/dist/workflow/WorkflowNormalizer.js +469 -19
- package/dist/workflow/WorkflowNormalizer.js.map +1 -1
- package/dist/workflow/WorkflowRegistry.d.ts +122 -0
- package/dist/workflow/WorkflowRegistry.js +121 -0
- package/dist/workflow/WorkflowRegistry.js.map +1 -1
- package/dist/workflow/sampleBody.d.ts +54 -0
- package/dist/workflow/sampleBody.js +320 -0
- package/dist/workflow/sampleBody.js.map +1 -0
- package/package.json +3 -8
- package/dist/adapters/HttpRuntimeAdapter.d.ts +0 -79
- package/dist/adapters/HttpRuntimeAdapter.js +0 -233
- package/dist/adapters/HttpRuntimeAdapter.js.map +0 -1
|
@@ -1,6 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier 2 #7 — debounce coordinator. Tier C #1 — cross-process awareness.
|
|
3
|
+
*
|
|
4
|
+
* Coalesces rapid same-key triggers into a single delayed run. Modes:
|
|
5
|
+
*
|
|
6
|
+
* - `"trailing"` (default): each ping resets a timer; the run fires
|
|
7
|
+
* after `delayMs` of silence. Latest payload wins (within a single
|
|
8
|
+
* owning process). `maxDelayMs` bounds tail latency — even with
|
|
9
|
+
* continuous pings, the run fires after `maxDelayMs` from the FIRST
|
|
10
|
+
* ping.
|
|
11
|
+
* - `"leading"`: the first ping fires immediately. Subsequent pings
|
|
12
|
+
* within `delayMs` are dropped. Window resets after `delayMs` of
|
|
13
|
+
* silence.
|
|
14
|
+
*
|
|
15
|
+
* Process-wide singleton. **Default**: in-memory only — fast,
|
|
16
|
+
* synchronous internally. **Cross-process mode (Tier C #1)**: install a
|
|
17
|
+
* `DebounceBackend` via `setBackend()` and the coordinator routes
|
|
18
|
+
* register/cancel through the backend with the same outcome surface,
|
|
19
|
+
* keeping a local timer + closure for the OWNING process. The
|
|
20
|
+
* `register()` API is async — callers must `await` it.
|
|
21
|
+
*
|
|
22
|
+
* Latest-payload semantics in cross-process mode: **owner-local**.
|
|
23
|
+
* Pings from a non-owning process bump pingCount + push scheduledAt in
|
|
24
|
+
* the shared doc but do NOT contribute their payload — only the
|
|
25
|
+
* owning process's captured `onFire` closure fires. Cross-process
|
|
26
|
+
* latest-payload-wins is deferred (would require persisting each
|
|
27
|
+
* ping's payload to the shared doc; tracked in BACKLOG).
|
|
28
|
+
*/
|
|
29
|
+
import { randomUUID } from "node:crypto";
|
|
30
|
+
const DEFAULT_OWNER_LEASE_MS = 60_000;
|
|
1
31
|
export class DebounceCoordinator {
|
|
2
32
|
static instance = null;
|
|
3
33
|
states = new Map();
|
|
34
|
+
backend = null;
|
|
35
|
+
/** Process identity for cross-process owner-lease attribution. Stable for the lifetime of the singleton. */
|
|
36
|
+
processId = randomUUID();
|
|
37
|
+
ownerLeaseMs = DEFAULT_OWNER_LEASE_MS;
|
|
4
38
|
static getInstance() {
|
|
5
39
|
if (!DebounceCoordinator.instance) {
|
|
6
40
|
DebounceCoordinator.instance = new DebounceCoordinator();
|
|
@@ -12,10 +46,41 @@ export class DebounceCoordinator {
|
|
|
12
46
|
DebounceCoordinator.instance?.clear();
|
|
13
47
|
DebounceCoordinator.instance = null;
|
|
14
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Install a cross-process backend. Set via `HttpTrigger.listen()` /
|
|
51
|
+
* `WorkerTrigger.listen()` when `BLOK_DEBOUNCE_BACKEND` is configured.
|
|
52
|
+
* Pass `null` to revert to the in-memory fast path.
|
|
53
|
+
*/
|
|
54
|
+
setBackend(backend) {
|
|
55
|
+
this.backend = backend;
|
|
56
|
+
}
|
|
57
|
+
getBackend() {
|
|
58
|
+
return this.backend;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Override the owner-lease duration. Used by `HttpTrigger.listen()` /
|
|
62
|
+
* `WorkerTrigger.listen()` to apply `BLOK_DEBOUNCE_OWNER_LEASE_MS`.
|
|
63
|
+
*/
|
|
64
|
+
setOwnerLeaseMs(ms) {
|
|
65
|
+
if (Number.isFinite(ms) && ms > 0) {
|
|
66
|
+
this.ownerLeaseMs = ms;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
15
69
|
bucket(workflowName, debounceKey) {
|
|
16
70
|
return `${workflowName}\x1f${debounceKey}`;
|
|
17
71
|
}
|
|
18
|
-
register(opts) {
|
|
72
|
+
async register(opts) {
|
|
73
|
+
if (this.backend) {
|
|
74
|
+
return this.registerCrossProcess(opts);
|
|
75
|
+
}
|
|
76
|
+
return this.registerLocal(opts);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* In-memory fast path — preserved exactly from the v1 single-process
|
|
80
|
+
* implementation. Synchronous internally; the outer signature is
|
|
81
|
+
* async so callers don't need to discriminate.
|
|
82
|
+
*/
|
|
83
|
+
registerLocal(opts) {
|
|
19
84
|
const now = opts.__now ?? Date.now();
|
|
20
85
|
const bucketKey = this.bucket(opts.workflowName, opts.debounceKey);
|
|
21
86
|
const existing = this.states.get(bucketKey);
|
|
@@ -66,7 +131,7 @@ export class DebounceCoordinator {
|
|
|
66
131
|
const naiveDeadline = now + opts.delayMs;
|
|
67
132
|
const dispatchAt = existing.maxDelayDeadline !== undefined ? Math.min(naiveDeadline, existing.maxDelayDeadline) : naiveDeadline;
|
|
68
133
|
const wait = Math.max(0, dispatchAt - now);
|
|
69
|
-
existing.timer = setTimeout(() => this.
|
|
134
|
+
existing.timer = setTimeout(() => this.fireTrailingLocal(bucketKey), wait);
|
|
70
135
|
return {
|
|
71
136
|
outcome: "coalesce",
|
|
72
137
|
activeRunId: existing.activeRunId,
|
|
@@ -90,7 +155,7 @@ export class DebounceCoordinator {
|
|
|
90
155
|
const naiveDeadline = now + opts.delayMs;
|
|
91
156
|
const dispatchAt = state.maxDelayDeadline !== undefined ? Math.min(naiveDeadline, state.maxDelayDeadline) : naiveDeadline;
|
|
92
157
|
const wait = Math.max(0, dispatchAt - now);
|
|
93
|
-
state.timer = setTimeout(() => this.
|
|
158
|
+
state.timer = setTimeout(() => this.fireTrailingLocal(bucketKey), wait);
|
|
94
159
|
this.states.set(bucketKey, state);
|
|
95
160
|
return {
|
|
96
161
|
outcome: "schedule-trailing",
|
|
@@ -99,7 +164,7 @@ export class DebounceCoordinator {
|
|
|
99
164
|
pingCount: 1,
|
|
100
165
|
};
|
|
101
166
|
}
|
|
102
|
-
|
|
167
|
+
fireTrailingLocal(bucketKey) {
|
|
103
168
|
const state = this.states.get(bucketKey);
|
|
104
169
|
if (!state)
|
|
105
170
|
return;
|
|
@@ -110,26 +175,182 @@ export class DebounceCoordinator {
|
|
|
110
175
|
});
|
|
111
176
|
}
|
|
112
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Cross-process path — delegates ownership to the backend and uses
|
|
180
|
+
* a local timer + closure for the OWNING process.
|
|
181
|
+
*
|
|
182
|
+
* Three outcomes from the backend:
|
|
183
|
+
* - `owner-new`: this process is the new owner. Start a local timer
|
|
184
|
+
* + closure; on fire, atomically finalize via the backend.
|
|
185
|
+
* - `owner-extend`: this process is already the owner. Cancel +
|
|
186
|
+
* restart the local timer at the new scheduledAt; refresh closure.
|
|
187
|
+
* - `coalesce`: another process owns the window. Just return.
|
|
188
|
+
*
|
|
189
|
+
* Leading mode: `owner-new` translates to `fire-immediate` (caller
|
|
190
|
+
* fires synchronously); `owner-extend`/`coalesce` translate to
|
|
191
|
+
* `coalesce`.
|
|
192
|
+
*/
|
|
193
|
+
async registerCrossProcess(opts) {
|
|
194
|
+
const backend = this.backend;
|
|
195
|
+
if (!backend)
|
|
196
|
+
return this.registerLocal(opts);
|
|
197
|
+
const now = opts.__now ?? Date.now();
|
|
198
|
+
const bucketKey = this.bucket(opts.workflowName, opts.debounceKey);
|
|
199
|
+
let res;
|
|
200
|
+
try {
|
|
201
|
+
res = await backend.registerPing({
|
|
202
|
+
workflowName: opts.workflowName,
|
|
203
|
+
debounceKey: opts.debounceKey,
|
|
204
|
+
mode: opts.mode,
|
|
205
|
+
delayMs: opts.delayMs,
|
|
206
|
+
maxDelayMs: opts.maxDelayMs,
|
|
207
|
+
runId: opts.runId,
|
|
208
|
+
processId: this.processId,
|
|
209
|
+
ownerLeaseMs: this.ownerLeaseMs,
|
|
210
|
+
now,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
// Fail-open — fall back to local in-memory window. Same posture
|
|
215
|
+
// as the concurrency-backend fail-fast path (deny-on-error) but
|
|
216
|
+
// debounce isn't a safety gate — better to admit the ping than
|
|
217
|
+
// drop it on a transient broker outage.
|
|
218
|
+
console.warn(`[blok][scheduling] debounce backend registerPing failed for ${bucketKey}: ${err instanceof Error ? err.message : String(err)}; falling back to in-memory window`);
|
|
219
|
+
return this.registerLocal(opts);
|
|
220
|
+
}
|
|
221
|
+
// === Leading mode ===
|
|
222
|
+
if (opts.mode === "leading") {
|
|
223
|
+
if (res.outcome === "owner-new") {
|
|
224
|
+
// Caller fires synchronously; we don't keep a local timer for
|
|
225
|
+
// leading mode (the backend's owner-lease IS the window).
|
|
226
|
+
return { outcome: "fire-immediate", activeRunId: opts.runId, pingCount: res.pingCount };
|
|
227
|
+
}
|
|
228
|
+
return { outcome: "coalesce", activeRunId: res.activeRunId, pingCount: res.pingCount };
|
|
229
|
+
}
|
|
230
|
+
// === Trailing mode ===
|
|
231
|
+
if (res.outcome === "owner-new") {
|
|
232
|
+
// New trailing window owned by this process. Capture the closure +
|
|
233
|
+
// start a local timer to fire at backend-decided scheduledAt.
|
|
234
|
+
this.installOwnerTimer(bucketKey, opts, res.scheduledAt, now);
|
|
235
|
+
return {
|
|
236
|
+
outcome: "schedule-trailing",
|
|
237
|
+
activeRunId: opts.runId,
|
|
238
|
+
scheduledAt: res.scheduledAt,
|
|
239
|
+
pingCount: res.pingCount,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (res.outcome === "owner-extend") {
|
|
243
|
+
// We still own. Replace the captured closure (latest payload
|
|
244
|
+
// wins within this process) + reschedule.
|
|
245
|
+
this.installOwnerTimer(bucketKey, opts, res.scheduledAt, now);
|
|
246
|
+
return {
|
|
247
|
+
outcome: "coalesce",
|
|
248
|
+
activeRunId: res.activeRunId,
|
|
249
|
+
scheduledAt: res.scheduledAt,
|
|
250
|
+
pingCount: res.pingCount,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
// outcome === "coalesce" — another process owns. Do not install a
|
|
254
|
+
// local timer; the owning process drives the fire.
|
|
255
|
+
return {
|
|
256
|
+
outcome: "coalesce",
|
|
257
|
+
activeRunId: res.activeRunId,
|
|
258
|
+
scheduledAt: res.scheduledAt,
|
|
259
|
+
pingCount: res.pingCount,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
installOwnerTimer(bucketKey, opts, scheduledAt, now) {
|
|
263
|
+
const existing = this.states.get(bucketKey);
|
|
264
|
+
if (existing?.timer)
|
|
265
|
+
clearTimeout(existing.timer);
|
|
266
|
+
const state = {
|
|
267
|
+
bucketKey,
|
|
268
|
+
mode: opts.mode,
|
|
269
|
+
delayMs: opts.delayMs,
|
|
270
|
+
maxDelayMs: opts.maxDelayMs,
|
|
271
|
+
firstPingAt: existing?.firstPingAt ?? now,
|
|
272
|
+
lastPingAt: now,
|
|
273
|
+
pingCount: (existing?.pingCount ?? 0) + 1,
|
|
274
|
+
activeRunId: opts.runId,
|
|
275
|
+
maxDelayDeadline: existing?.maxDelayDeadline ?? (opts.maxDelayMs !== undefined ? now + opts.maxDelayMs : undefined),
|
|
276
|
+
onFire: opts.onFire,
|
|
277
|
+
};
|
|
278
|
+
const wait = Math.max(0, scheduledAt - now);
|
|
279
|
+
state.timer = setTimeout(() => {
|
|
280
|
+
void this.fireTrailingCrossProcess(bucketKey, opts.workflowName, opts.debounceKey, opts.runId);
|
|
281
|
+
}, wait);
|
|
282
|
+
this.states.set(bucketKey, state);
|
|
283
|
+
}
|
|
284
|
+
async fireTrailingCrossProcess(bucketKey, workflowName, debounceKey, runId) {
|
|
285
|
+
const backend = this.backend;
|
|
286
|
+
const state = this.states.get(bucketKey);
|
|
287
|
+
if (!backend || !state)
|
|
288
|
+
return;
|
|
289
|
+
const now = Date.now();
|
|
290
|
+
let result;
|
|
291
|
+
try {
|
|
292
|
+
result = await backend.finalize(workflowName, debounceKey, runId, now);
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
// Treat as abandoned — owner-lease will eventually expire and
|
|
296
|
+
// another process can take over. Don't fire to avoid duplicate
|
|
297
|
+
// dispatch.
|
|
298
|
+
console.warn(`[blok][scheduling] debounce backend finalize failed for ${bucketKey}: ${err instanceof Error ? err.message : String(err)}; abandoning local owner state`);
|
|
299
|
+
this.states.delete(bucketKey);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (result.finalize === "fire") {
|
|
303
|
+
this.states.delete(bucketKey);
|
|
304
|
+
if (state.onFire) {
|
|
305
|
+
void state.onFire().catch((err) => {
|
|
306
|
+
console.error(`[blok][scheduling] DebounceCoordinator cross-process fire failed for key ${bucketKey}:`, err instanceof Error ? err.stack || err.message : err);
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (result.finalize === "reschedule") {
|
|
312
|
+
// Coalesce pings from other processes pushed scheduledAt forward.
|
|
313
|
+
// Reschedule local timer; closure stays.
|
|
314
|
+
if (state.timer)
|
|
315
|
+
clearTimeout(state.timer);
|
|
316
|
+
const wait = Math.max(0, result.scheduledAt - now);
|
|
317
|
+
state.timer = setTimeout(() => {
|
|
318
|
+
void this.fireTrailingCrossProcess(bucketKey, workflowName, debounceKey, runId);
|
|
319
|
+
}, wait);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
// finalize === "abandoned" — lease expired; another process took
|
|
323
|
+
// over. Drop the closure silently.
|
|
324
|
+
this.states.delete(bucketKey);
|
|
325
|
+
}
|
|
113
326
|
/** Cancel an active window without firing. Returns true if cancelled. */
|
|
114
|
-
cancel(workflowName, debounceKey) {
|
|
327
|
+
async cancel(workflowName, debounceKey) {
|
|
115
328
|
const bucketKey = this.bucket(workflowName, debounceKey);
|
|
116
329
|
const state = this.states.get(bucketKey);
|
|
117
|
-
if (
|
|
118
|
-
return false;
|
|
119
|
-
if (state.timer)
|
|
330
|
+
if (state?.timer)
|
|
120
331
|
clearTimeout(state.timer);
|
|
121
|
-
this.states.delete(bucketKey);
|
|
122
|
-
|
|
332
|
+
const hadLocal = this.states.delete(bucketKey);
|
|
333
|
+
if (this.backend) {
|
|
334
|
+
try {
|
|
335
|
+
const cancelled = await this.backend.cancel(workflowName, debounceKey);
|
|
336
|
+
return cancelled || hadLocal;
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
console.warn(`[blok][scheduling] debounce backend cancel failed for ${bucketKey}: ${err instanceof Error ? err.message : String(err)}`);
|
|
340
|
+
return hadLocal;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return hadLocal;
|
|
123
344
|
}
|
|
124
|
-
/** Number of active debounce windows. Tests + observability. */
|
|
345
|
+
/** Number of active LOCAL debounce windows. Tests + observability. Excludes cross-process windows owned by other processes. */
|
|
125
346
|
size() {
|
|
126
347
|
return this.states.size;
|
|
127
348
|
}
|
|
128
|
-
/** True if a window
|
|
349
|
+
/** True if THIS process has a local window for `(workflow, key)`. Excludes windows owned by other processes. */
|
|
129
350
|
has(workflowName, debounceKey) {
|
|
130
351
|
return this.states.has(this.bucket(workflowName, debounceKey));
|
|
131
352
|
}
|
|
132
|
-
/** Cancel everything without firing. */
|
|
353
|
+
/** Cancel everything without firing. Local state only — cross-process buckets fall back to lease-expiry. */
|
|
133
354
|
clear() {
|
|
134
355
|
for (const state of this.states.values()) {
|
|
135
356
|
if (state.timer)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DebounceCoordinator.js","sourceRoot":"","sources":["../../src/scheduling/DebounceCoordinator.ts"],"names":[],"mappings":"AA4FA,MAAM,OAAO,mBAAmB;IACvB,MAAM,CAAC,QAAQ,GAA+B,IAAI,CAAC;IAEnD,MAAM,GAA+B,IAAI,GAAG,EAAE,CAAC;IAEvD,MAAM,CAAC,WAAW;QACjB,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,CAAC;YACnC,mBAAmB,CAAC,QAAQ,GAAG,IAAI,mBAAmB,EAAE,CAAC;QAC1D,CAAC;QACD,OAAO,mBAAmB,CAAC,QAAQ,CAAC;IACrC,CAAC;IAED,yDAAyD;IACzD,MAAM,CAAC,aAAa;QACnB,mBAAmB,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC;QACtC,mBAAmB,CAAC,QAAQ,GAAG,IAAI,CAAC;IACrC,CAAC;IAEO,MAAM,CAAC,YAAoB,EAAE,WAAmB;QACvD,OAAO,GAAG,YAAY,OAAO,WAAW,EAAE,CAAC;IAC5C,CAAC;IAED,QAAQ,CAAC,IAA0B;QAClC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QACrC,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QACnE,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAE5C,uBAAuB;QACvB,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC7B,IAAI,QAAQ,EAAE,CAAC;gBACd,qDAAqD;gBACrD,QAAQ,CAAC,SAAS,IAAI,CAAC,CAAC;gBACxB,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;gBAC1B,OAAO;oBACN,OAAO,EAAE,UAAU;oBACnB,WAAW,EAAE,QAAQ,CAAC,WAAW;oBACjC,SAAS,EAAE,QAAQ,CAAC,SAAS;iBAC7B,CAAC;YACH,CAAC;YACD,kEAAkE;YAClE,qDAAqD;YACrD,MAAM,KAAK,GAAkB;gBAC5B,SAAS;gBACT,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,WAAW,EAAE,GAAG;gBAChB,UAAU,EAAE,GAAG;gBACf,SAAS,EAAE,CAAC;gBACZ,WAAW,EAAE,IAAI,CAAC,KAAK;aACvB,CAAC;YACF,kEAAkE;YAClE,gCAAgC;YAChC,KAAK,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACvC,IAAI,GAAG,IAAI,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;oBACxD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBAC/B,CAAC;YACF,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;YACjB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YAClC,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;QAC7E,CAAC;QAED,wBAAwB;QACxB,IAAI,QAAQ,EAAE,CAAC;YACd,iEAAiE;YACjE,0CAA0C;YAC1C,IAAI,QAAQ,CAAC,KAAK;gBAAE,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YACjD,QAAQ,CAAC,SAAS,IAAI,CAAC,CAAC;YACxB,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAC;YAC1B,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;YAE9B,MAAM,aAAa,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC;YACzC,MAAM,UAAU,GACf,QAAQ,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;YAC9G,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,GAAG,CAAC,CAAC;YAC3C,QAAQ,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,CAAC;YACtE,OAAO;gBACN,OAAO,EAAE,UAAU;gBACnB,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,WAAW,EAAE,UAAU;gBACvB,SAAS,EAAE,QAAQ,CAAC,SAAS;aAC7B,CAAC;QACH,CAAC;QAED,uBAAuB;QACvB,MAAM,KAAK,GAAkB;YAC5B,SAAS;YACT,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,WAAW,EAAE,GAAG;YAChB,UAAU,EAAE,GAAG;YACf,SAAS,EAAE,CAAC;YACZ,WAAW,EAAE,IAAI,CAAC,KAAK;YACvB,gBAAgB,EAAE,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;YACnF,MAAM,EAAE,IAAI,CAAC,MAAM;SACnB,CAAC;QACF,MAAM,aAAa,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC;QACzC,MAAM,UAAU,GACf,KAAK,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;QACxG,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,GAAG,CAAC,CAAC;QAC3C,KAAK,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,CAAC;QACnE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAClC,OAAO;YACN,OAAO,EAAE,mBAAmB;YAC5B,WAAW,EAAE,IAAI,CAAC,KAAK;YACvB,WAAW,EAAE,UAAU;YACvB,SAAS,EAAE,CAAC;SACZ,CAAC;IACH,CAAC;IAEO,YAAY,CAAC,SAAiB;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACzC,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC9B,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YAClB,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;gBAC1C,OAAO,CAAC,KAAK,CACZ,uEAAuE,SAAS,GAAG,EACnF,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CACrD,CAAC;YACH,CAAC,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED,yEAAyE;IACzE,MAAM,CAAC,YAAoB,EAAE,WAAmB;QAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACzC,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,IAAI,KAAK,CAAC,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC9B,OAAO,IAAI,CAAC;IACb,CAAC;IAED,gEAAgE;IAChE,IAAI;QACH,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;IACzB,CAAC;IAED,sDAAsD;IACtD,GAAG,CAAC,YAAoB,EAAE,WAAmB;QAC5C,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,wCAAwC;IACxC,KAAK;QACJ,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1C,IAAI,KAAK,CAAC,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5C,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC"}
|
|
1
|
+
{"version":3,"file":"DebounceCoordinator.js","sourceRoot":"","sources":["../../src/scheduling/DebounceCoordinator.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,6 +24,18 @@ export interface DeferredScheduleOptions {
|
|
|
24
24
|
export declare class DeferredRunScheduler {
|
|
25
25
|
private static instance;
|
|
26
26
|
private entries;
|
|
27
|
+
/**
|
|
28
|
+
* Tier C #2 — stable per-process identity used for claim ownership.
|
|
29
|
+
* Generated once per scheduler instance; surviving across resets is
|
|
30
|
+
* intentional (a "process restart" in production gets a fresh
|
|
31
|
+
* scheduler singleton and therefore a fresh processId — peers' stale
|
|
32
|
+
* claims expire after the lease).
|
|
33
|
+
*/
|
|
34
|
+
private readonly processId;
|
|
35
|
+
/** Tier C #2 — running heartbeat timer; one per scheduler instance. */
|
|
36
|
+
private heartbeatTimer;
|
|
37
|
+
/** Tier C #2 — count of persisted entries currently registered. Heartbeat is only active when > 0. */
|
|
38
|
+
private persistedEntryCount;
|
|
27
39
|
static getInstance(): DeferredRunScheduler;
|
|
28
40
|
/** Test-only — reset the singleton. Cancels all pending timers. */
|
|
29
41
|
static resetInstance(): void;
|
|
@@ -65,4 +77,20 @@ export declare class DeferredRunScheduler {
|
|
|
65
77
|
drainAll(): Promise<void>;
|
|
66
78
|
/** Cancel everything without dispatching. */
|
|
67
79
|
clear(): void;
|
|
80
|
+
/**
|
|
81
|
+
* Stable per-process identity used for the claim API. Trigger boot
|
|
82
|
+
* recovery passes this to `RunStore.claimDispatches(processId, …)`
|
|
83
|
+
* so peers can recognize each other's claims.
|
|
84
|
+
*/
|
|
85
|
+
getProcessId(): string;
|
|
86
|
+
private maybeStartHeartbeat;
|
|
87
|
+
private maybeStopHeartbeat;
|
|
88
|
+
private runHeartbeat;
|
|
68
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Public helper that returns the operator-configured claim-lease in
|
|
92
|
+
* ms. Used by `HttpTrigger.recoverDispatches()` to pass the lease to
|
|
93
|
+
* `RunStore.claimDispatches()`. Centralized so the env var name is
|
|
94
|
+
* defined in one place.
|
|
95
|
+
*/
|
|
96
|
+
export declare function getSchedulerClaimLeaseMs(): number;
|
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tier 2 #5 — in-memory scheduler for deferred workflow runs. Tier 2
|
|
3
3
|
* #5+#7 follow-up adds optional sqlite-backed durability via the
|
|
4
|
-
* {@link DeferredScheduleOptions.persist} parameter.
|
|
4
|
+
* {@link DeferredScheduleOptions.persist} parameter. Tier C #2 adds
|
|
5
|
+
* cross-process claim heartbeats so multi-process deployments sharing
|
|
6
|
+
* a PG store don't double-fire the same dispatch.
|
|
5
7
|
*
|
|
6
8
|
* Process-wide singleton; obtained via {@link DeferredRunScheduler.getInstance}.
|
|
7
9
|
* Reset between tests via {@link DeferredRunScheduler.resetInstance}.
|
|
8
10
|
*/
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
9
12
|
import { RunTracker } from "../tracing/RunTracker";
|
|
13
|
+
const DEFAULT_CLAIM_LEASE_MS = 60_000;
|
|
14
|
+
const DEFAULT_HEARTBEAT_INTERVAL_MS = 20_000;
|
|
10
15
|
export class DeferredRunScheduler {
|
|
11
16
|
static instance = null;
|
|
12
17
|
entries = new Map();
|
|
18
|
+
/**
|
|
19
|
+
* Tier C #2 — stable per-process identity used for claim ownership.
|
|
20
|
+
* Generated once per scheduler instance; surviving across resets is
|
|
21
|
+
* intentional (a "process restart" in production gets a fresh
|
|
22
|
+
* scheduler singleton and therefore a fresh processId — peers' stale
|
|
23
|
+
* claims expire after the lease).
|
|
24
|
+
*/
|
|
25
|
+
processId = randomUUID();
|
|
26
|
+
/** Tier C #2 — running heartbeat timer; one per scheduler instance. */
|
|
27
|
+
heartbeatTimer = null;
|
|
28
|
+
/** Tier C #2 — count of persisted entries currently registered. Heartbeat is only active when > 0. */
|
|
29
|
+
persistedEntryCount = 0;
|
|
13
30
|
static getInstance() {
|
|
14
31
|
if (!DeferredRunScheduler.instance) {
|
|
15
32
|
DeferredRunScheduler.instance = new DeferredRunScheduler();
|
|
@@ -69,8 +86,11 @@ export class DeferredRunScheduler {
|
|
|
69
86
|
this.entries.delete(runId);
|
|
70
87
|
// Best-effort delete the persisted row before invoking dispatchFn —
|
|
71
88
|
// dispatch will write the run's terminal status separately.
|
|
72
|
-
if (persisted)
|
|
89
|
+
if (persisted) {
|
|
73
90
|
this.deletePersistedRow(runId);
|
|
91
|
+
this.persistedEntryCount = Math.max(0, this.persistedEntryCount - 1);
|
|
92
|
+
this.maybeStopHeartbeat();
|
|
93
|
+
}
|
|
74
94
|
void dispatchFn().catch((err) => {
|
|
75
95
|
console.error(`[blok][scheduling] DeferredRunScheduler dispatch failed for run ${runId}:`, err instanceof Error ? err.stack || err.message : err);
|
|
76
96
|
});
|
|
@@ -78,7 +98,20 @@ export class DeferredRunScheduler {
|
|
|
78
98
|
// `Node` will keep the event loop alive for pending timers — that's
|
|
79
99
|
// the desired behavior for delayed runs in long-running services.
|
|
80
100
|
// `unref()` would be wrong here.
|
|
101
|
+
const wasPersisted = existing?.persisted === true;
|
|
81
102
|
this.entries.set(runId, { runId, dispatchAt, timer, dispatchFn, persisted });
|
|
103
|
+
// Tier C #2 — track persisted entry count + manage heartbeat
|
|
104
|
+
// lifecycle. The heartbeat only runs while we have ≥1 persisted
|
|
105
|
+
// entry (otherwise there's nothing to keep claimed). Replace doesn't
|
|
106
|
+
// change the count.
|
|
107
|
+
if (persisted && !wasPersisted) {
|
|
108
|
+
this.persistedEntryCount++;
|
|
109
|
+
this.maybeStartHeartbeat();
|
|
110
|
+
}
|
|
111
|
+
else if (!persisted && wasPersisted) {
|
|
112
|
+
this.persistedEntryCount = Math.max(0, this.persistedEntryCount - 1);
|
|
113
|
+
this.maybeStopHeartbeat();
|
|
114
|
+
}
|
|
82
115
|
}
|
|
83
116
|
/**
|
|
84
117
|
* Cancel a pending dispatch. Returns true if the entry existed and
|
|
@@ -99,8 +132,11 @@ export class DeferredRunScheduler {
|
|
|
99
132
|
}
|
|
100
133
|
clearTimeout(entry.timer);
|
|
101
134
|
this.entries.delete(runId);
|
|
102
|
-
if (entry.persisted)
|
|
135
|
+
if (entry.persisted) {
|
|
103
136
|
this.deletePersistedRow(runId);
|
|
137
|
+
this.persistedEntryCount = Math.max(0, this.persistedEntryCount - 1);
|
|
138
|
+
this.maybeStopHeartbeat();
|
|
139
|
+
}
|
|
104
140
|
return true;
|
|
105
141
|
}
|
|
106
142
|
deletePersistedRow(runId) {
|
|
@@ -149,6 +185,72 @@ export class DeferredRunScheduler {
|
|
|
149
185
|
for (const entry of this.entries.values())
|
|
150
186
|
clearTimeout(entry.timer);
|
|
151
187
|
this.entries.clear();
|
|
188
|
+
this.persistedEntryCount = 0;
|
|
189
|
+
this.maybeStopHeartbeat();
|
|
152
190
|
}
|
|
191
|
+
// === Tier C #2 — cross-process claim heartbeat ===
|
|
192
|
+
/**
|
|
193
|
+
* Stable per-process identity used for the claim API. Trigger boot
|
|
194
|
+
* recovery passes this to `RunStore.claimDispatches(processId, …)`
|
|
195
|
+
* so peers can recognize each other's claims.
|
|
196
|
+
*/
|
|
197
|
+
getProcessId() {
|
|
198
|
+
return this.processId;
|
|
199
|
+
}
|
|
200
|
+
maybeStartHeartbeat() {
|
|
201
|
+
if (this.heartbeatTimer !== null)
|
|
202
|
+
return;
|
|
203
|
+
if (this.persistedEntryCount === 0)
|
|
204
|
+
return;
|
|
205
|
+
if (process.env.BLOK_SCHEDULER_CLAIM_DISABLED === "1")
|
|
206
|
+
return;
|
|
207
|
+
const intervalMs = readEnvInt("BLOK_SCHEDULER_HEARTBEAT_INTERVAL_MS", DEFAULT_HEARTBEAT_INTERVAL_MS);
|
|
208
|
+
this.heartbeatTimer = setInterval(() => {
|
|
209
|
+
this.runHeartbeat();
|
|
210
|
+
}, intervalMs);
|
|
211
|
+
// Don't keep the event loop alive solely for the heartbeat —
|
|
212
|
+
// the persisted entries' timers already do that. `unref()`
|
|
213
|
+
// avoids blocking shutdown when no entries are pending.
|
|
214
|
+
this.heartbeatTimer.unref?.();
|
|
215
|
+
}
|
|
216
|
+
maybeStopHeartbeat() {
|
|
217
|
+
if (this.heartbeatTimer === null)
|
|
218
|
+
return;
|
|
219
|
+
if (this.persistedEntryCount > 0)
|
|
220
|
+
return;
|
|
221
|
+
clearInterval(this.heartbeatTimer);
|
|
222
|
+
this.heartbeatTimer = null;
|
|
223
|
+
}
|
|
224
|
+
runHeartbeat() {
|
|
225
|
+
const tracker = RunTracker.getInstance();
|
|
226
|
+
if (!tracker.active)
|
|
227
|
+
return;
|
|
228
|
+
try {
|
|
229
|
+
tracker.getStore().heartbeatClaims(this.processId, Date.now());
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
// Heartbeat failures are non-fatal — the lease will expire if
|
|
233
|
+
// they continue, and a peer will take over. Log + continue.
|
|
234
|
+
console.warn(`[blok][scheduling] heartbeatClaims failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function readEnvInt(name, fallback) {
|
|
239
|
+
const raw = process.env[name];
|
|
240
|
+
if (raw && /^\d+$/.test(raw)) {
|
|
241
|
+
const n = Number(raw);
|
|
242
|
+
if (Number.isFinite(n) && n > 0)
|
|
243
|
+
return n;
|
|
244
|
+
}
|
|
245
|
+
return fallback;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Public helper that returns the operator-configured claim-lease in
|
|
249
|
+
* ms. Used by `HttpTrigger.recoverDispatches()` to pass the lease to
|
|
250
|
+
* `RunStore.claimDispatches()`. Centralized so the env var name is
|
|
251
|
+
* defined in one place.
|
|
252
|
+
*/
|
|
253
|
+
export function getSchedulerClaimLeaseMs() {
|
|
254
|
+
return readEnvInt("BLOK_SCHEDULER_CLAIM_LEASE_MS", DEFAULT_CLAIM_LEASE_MS);
|
|
153
255
|
}
|
|
154
256
|
//# sourceMappingURL=DeferredRunScheduler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeferredRunScheduler.js","sourceRoot":"","sources":["../../src/scheduling/DeferredRunScheduler.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"DeferredRunScheduler.js","sourceRoot":"","sources":["../../src/scheduling/DeferredRunScheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier C #1 · NATS KV-backed debounce backend.
|
|
3
|
+
*
|
|
4
|
+
* Coordinates per-(workflow, debounceKey) window state across processes
|
|
5
|
+
* via a single NATS JetStream KV value per bucket with revision-based
|
|
6
|
+
* compare-and-swap (OCC). Mirrors the storage model of
|
|
7
|
+
* `NatsKvConcurrencyBackend` but with a different document shape (one
|
|
8
|
+
* window per bucket, not a leases array).
|
|
9
|
+
*
|
|
10
|
+
* Acquire / extend / coalesce: a bounded CAS loop (10 retries) that
|
|
11
|
+
* reads → decides ownership → atomically writes. On retry exhaustion,
|
|
12
|
+
* fall back to admitting the ping as a `coalesce` against the current
|
|
13
|
+
* owner — debounce is not a safety gate, so we'd rather over-coalesce
|
|
14
|
+
* than drop pings on a contention spike.
|
|
15
|
+
*
|
|
16
|
+
* Finalize: same OCC pattern. The owning process atomically reads the
|
|
17
|
+
* doc, confirms it still owns AND `now >= scheduledAt`, and atomically
|
|
18
|
+
* deletes. On lease handoff, the owner discovers it no longer owns and
|
|
19
|
+
* abandons silently.
|
|
20
|
+
*
|
|
21
|
+
* **Owner-local payload**: this backend tracks `pingCount`,
|
|
22
|
+
* `lastPingAt`, and `scheduledAt` only — not the payload. Cross-process
|
|
23
|
+
* latest-payload-wins is a deferred follow-up.
|
|
24
|
+
*/
|
|
25
|
+
import type { DebounceBackend, DebounceFinalizeResult, DebounceRegisterBackendOpts, DebounceRegisterBackendResult } from "./DebounceBackend";
|
|
26
|
+
export interface NatsKvDebounceConfig {
|
|
27
|
+
servers: string[];
|
|
28
|
+
token?: string;
|
|
29
|
+
user?: string;
|
|
30
|
+
pass?: string;
|
|
31
|
+
bucketName: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function readNatsKvDebounceConfigFromEnv(): NatsKvDebounceConfig;
|
|
34
|
+
export declare class NatsKvDebounceBackend implements DebounceBackend {
|
|
35
|
+
readonly name = "nats-kv";
|
|
36
|
+
private nc;
|
|
37
|
+
private kv;
|
|
38
|
+
private readonly config;
|
|
39
|
+
private connected;
|
|
40
|
+
constructor(config?: Partial<NatsKvDebounceConfig>);
|
|
41
|
+
connect(): Promise<void>;
|
|
42
|
+
disconnect(): Promise<void>;
|
|
43
|
+
private bucketKey;
|
|
44
|
+
private encodeSegment;
|
|
45
|
+
private requireKv;
|
|
46
|
+
private computeScheduledAt;
|
|
47
|
+
registerPing(opts: DebounceRegisterBackendOpts): Promise<DebounceRegisterBackendResult>;
|
|
48
|
+
finalize(workflowName: string, debounceKey: string, runId: string, now: number): Promise<DebounceFinalizeResult>;
|
|
49
|
+
cancel(workflowName: string, debounceKey: string): Promise<boolean>;
|
|
50
|
+
purgeExpired(now: number): Promise<number>;
|
|
51
|
+
private safeGet;
|
|
52
|
+
private parseDoc;
|
|
53
|
+
}
|