@bastani/atomic 0.9.61 → 0.9.62
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 +28 -0
- package/dist/builtin/intercom/index.bundle.mjs +14 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +97 -32
- package/dist/builtin/workflows/src/extension/local-worker-entry.mjs +98 -33
- package/dist/builtin/workflows/src/index.js +97 -32
- package/dist/builtin/workflows/src/local-worker-entry.mjs +98 -33
- package/npm-shrinkwrap.json +32 -32
- package/package.json +3 -3
|
@@ -2378,15 +2378,12 @@ class JournalDurableBackend {
|
|
|
2378
2378
|
commitCheckpoint(checkpoint, options) {
|
|
2379
2379
|
if (options?.signal?.aborted)
|
|
2380
2380
|
return Promise.reject(new JournalOperationAbortedError(checkpoint.workflowId, options.signal.reason instanceof Error ? options.signal.reason : undefined));
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
yield* this.append(checkpoint.workflowId, { type: RUN_EVENT_TYPES.checkpointRecorded, payload: checkpoint }, {
|
|
2388
|
-
key: JSON.stringify([RUN_EVENT_TYPES.checkpointRecorded, checkpoint.kind, checkpoint.checkpointId])
|
|
2389
|
-
});
|
|
2381
|
+
try {
|
|
2382
|
+
validateCheckpointInvocationFingerprint(checkpoint);
|
|
2383
|
+
} catch (error) {
|
|
2384
|
+
return Promise.reject(error);
|
|
2385
|
+
}
|
|
2386
|
+
return this.queueAppend(checkpoint.workflowId, { type: RUN_EVENT_TYPES.checkpointRecorded, payload: checkpoint }, JSON.stringify([RUN_EVENT_TYPES.checkpointRecorded, checkpoint.kind, checkpoint.checkpointId]), true);
|
|
2390
2387
|
}
|
|
2391
2388
|
async commitAdditiveCheckpointBestEffort(checkpoint) {
|
|
2392
2389
|
try {
|
|
@@ -2523,7 +2520,7 @@ class JournalDurableBackend {
|
|
|
2523
2520
|
payload: { messageId, delivery, ...accounting === undefined ? {} : { accounting } }
|
|
2524
2521
|
});
|
|
2525
2522
|
}
|
|
2526
|
-
queueAppend(runId, event, key) {
|
|
2523
|
+
queueAppend(runId, event, key, requiresRegisteredRun = false) {
|
|
2527
2524
|
try {
|
|
2528
2525
|
this.ensureOpen();
|
|
2529
2526
|
this.assertExecutionActive();
|
|
@@ -2536,6 +2533,7 @@ class JournalDurableBackend {
|
|
|
2536
2533
|
batch.push({
|
|
2537
2534
|
event,
|
|
2538
2535
|
...key === undefined ? {} : { key },
|
|
2536
|
+
...requiresRegisteredRun ? { requiresRegisteredRun } : {},
|
|
2539
2537
|
context: AsyncLocalStorage.snapshot(),
|
|
2540
2538
|
resolve,
|
|
2541
2539
|
reject
|
|
@@ -2550,9 +2548,12 @@ class JournalDurableBackend {
|
|
|
2550
2548
|
}
|
|
2551
2549
|
return queued;
|
|
2552
2550
|
}
|
|
2553
|
-
*appendBatch(runId,
|
|
2554
|
-
if (this.appendBatches.get(runId) ===
|
|
2551
|
+
*appendBatch(runId, queued) {
|
|
2552
|
+
if (this.appendBatches.get(runId) === queued)
|
|
2555
2553
|
this.appendBatches.delete(runId);
|
|
2554
|
+
const batch = yield* this.registeredAppends(runId, queued);
|
|
2555
|
+
if (batch.length === 0)
|
|
2556
|
+
return;
|
|
2556
2557
|
const appendMany = runJournalAppendMany(this.journal);
|
|
2557
2558
|
if (appendMany === undefined || batch.length === 1) {
|
|
2558
2559
|
for (const append of batch) {
|
|
@@ -2590,6 +2591,25 @@ class JournalDurableBackend {
|
|
|
2590
2591
|
append.reject(failure);
|
|
2591
2592
|
}
|
|
2592
2593
|
}
|
|
2594
|
+
*registeredAppends(runId, batch) {
|
|
2595
|
+
if (!batch.some((append) => append.requiresRegisteredRun === true))
|
|
2596
|
+
return [...batch];
|
|
2597
|
+
let registered;
|
|
2598
|
+
try {
|
|
2599
|
+
registered = (yield* this.current(runId)) !== undefined;
|
|
2600
|
+
} catch (error) {
|
|
2601
|
+
for (const append of batch)
|
|
2602
|
+
if (append.requiresRegisteredRun === true)
|
|
2603
|
+
append.reject(error);
|
|
2604
|
+
return batch.filter((append) => append.requiresRegisteredRun !== true);
|
|
2605
|
+
}
|
|
2606
|
+
if (registered)
|
|
2607
|
+
return [...batch];
|
|
2608
|
+
for (const append of batch)
|
|
2609
|
+
if (append.requiresRegisteredRun === true)
|
|
2610
|
+
append.resolve();
|
|
2611
|
+
return batch.filter((append) => append.requiresRegisteredRun !== true);
|
|
2612
|
+
}
|
|
2593
2613
|
getToolOutput(...args) {
|
|
2594
2614
|
return this.projection.getToolOutput(...args);
|
|
2595
2615
|
}
|
|
@@ -17442,7 +17462,7 @@ class SqlResourceAdmission {
|
|
|
17442
17462
|
signal?.throwIfAborted();
|
|
17443
17463
|
const operation = randomUUID6();
|
|
17444
17464
|
const release = (reason = "resource cleanup confirmed") => this.storage.transaction(async (tx) => {
|
|
17445
|
-
await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = ? WHERE operation = ? AND state != 'released'`, reason, operation);
|
|
17465
|
+
await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = ? WHERE operation = ? AND (state != 'released' OR reason = ?)`, reason, operation, FENCED_WAITER_REASON);
|
|
17446
17466
|
});
|
|
17447
17467
|
release.bindProcess = async (identity) => {
|
|
17448
17468
|
if (kind !== "process" || identity.kind !== "unix-process-group" || !Number.isSafeInteger(identity.id) || identity.id <= 0)
|
|
@@ -17454,6 +17474,15 @@ class SqlResourceAdmission {
|
|
|
17454
17474
|
throw new Error("Resource process permit is no longer held");
|
|
17455
17475
|
});
|
|
17456
17476
|
};
|
|
17477
|
+
const keepWaiting = async (tx) => {
|
|
17478
|
+
const permit = await tx.get(`SELECT state, reason FROM ${this.permits} WHERE operation = ?`, operation);
|
|
17479
|
+
if (permit?.state === "released" && permit.reason === FENCED_WAITER_REASON)
|
|
17480
|
+
await tx.run(`UPDATE ${this.permits} SET state = 'waiting', reason = NULL WHERE operation = ?`, operation);
|
|
17481
|
+
else if (permit === undefined)
|
|
17482
|
+
throw new AdmissionHolderStoppedError("permit no longer recorded");
|
|
17483
|
+
else if (permit.state !== "waiting")
|
|
17484
|
+
throw new AdmissionHolderStoppedError(permit.reason === null ? undefined : String(permit.reason));
|
|
17485
|
+
};
|
|
17457
17486
|
await this.storage.transaction(async (tx) => {
|
|
17458
17487
|
await tx.run(`UPDATE ${this.owners} SET expires_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) + ? WHERE id = ?`, OWNER_LEASE_MS, this.owner);
|
|
17459
17488
|
await tx.run(`INSERT INTO ${this.permits}(operation, root, holder, kind, state, owner, attempt) VALUES (?, ?, ?, ?, 'waiting', ?, ?)`, operation, this.options.root, this.options.holder, kind, this.owner, this.options.attempt ? JSON.stringify({
|
|
@@ -17476,11 +17505,12 @@ class SqlResourceAdmission {
|
|
|
17476
17505
|
const granted = await this.storage.transaction(async (tx) => {
|
|
17477
17506
|
quarantined = undefined;
|
|
17478
17507
|
await tx.run(`UPDATE ${this.owners} SET expires_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) + ? WHERE id = ?`, OWNER_LEASE_MS, this.owner);
|
|
17508
|
+
await keepWaiting(tx);
|
|
17479
17509
|
const { root } = this.options;
|
|
17480
17510
|
if (!await this.assertCurrentAttempt(tx)) {
|
|
17481
17511
|
const waiting = await tx.run(`UPDATE ${this.permits} SET reason = ? WHERE operation = ? AND state = 'waiting'`, LATE_ATTEMPT_REASON, operation);
|
|
17482
17512
|
if (waiting.changes !== 1)
|
|
17483
|
-
throw new
|
|
17513
|
+
throw new AdmissionHolderStoppedError(undefined);
|
|
17484
17514
|
return false;
|
|
17485
17515
|
}
|
|
17486
17516
|
let reason = hostReason;
|
|
@@ -17523,7 +17553,7 @@ class SqlResourceAdmission {
|
|
|
17523
17553
|
const stageStart = kind === "session" && request?.delegated !== true;
|
|
17524
17554
|
const updated = await tx.run(`UPDATE ${this.permits} SET state = ?, reason = ?, granted_at = CASE WHEN ? IS NULL AND ? THEN ${NOW_MS} ELSE NULL END WHERE operation = ? AND state = 'waiting'`, reason ? "waiting" : "granted", reason ?? null, reason ?? null, stageStart ? 1 : 0, operation);
|
|
17525
17555
|
if (updated.changes !== 1)
|
|
17526
|
-
throw new
|
|
17556
|
+
throw new AdmissionHolderStoppedError(undefined);
|
|
17527
17557
|
return !reason;
|
|
17528
17558
|
});
|
|
17529
17559
|
if (granted) {
|
|
@@ -17565,7 +17595,7 @@ class SqlResourceAdmission {
|
|
|
17565
17595
|
WHERE p.state IN ('waiting', 'granted')`);
|
|
17566
17596
|
if (live.some((permit) => this.needsFencing(permit, fence)))
|
|
17567
17597
|
return true;
|
|
17568
|
-
return !!await db.get(`SELECT 1 AS prunable FROM ${this.permits} WHERE
|
|
17598
|
+
return !!await db.get(`SELECT 1 AS prunable FROM ${this.permits} r WHERE ${this.prunable} LIMIT 1`, RELEASED_PERMIT_RETENTION, FENCED_WAITER_REASON, FENCED_WAITER_RETENTION_MS);
|
|
17569
17599
|
});
|
|
17570
17600
|
if (!pending)
|
|
17571
17601
|
return;
|
|
@@ -17574,7 +17604,7 @@ class SqlResourceAdmission {
|
|
|
17574
17604
|
const owners = await tx.all(`SELECT o.* FROM ${this.owners} o WHERE o.id IN (SELECT owner FROM ${this.permits} WHERE state IN ('waiting', 'granted'))`);
|
|
17575
17605
|
for (const owner of owners) {
|
|
17576
17606
|
const { rebooted, stopped } = fence(owner);
|
|
17577
|
-
await tx.run(`UPDATE ${this.permits} SET state = 'released', reason =
|
|
17607
|
+
await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = ? WHERE owner = ? AND state = 'waiting' AND (? OR EXISTS (SELECT 1 FROM ${this.owners} WHERE id = ? AND expires_at < ${NOW_MS}))`, FENCED_WAITER_REASON, owner.id, stopped ? 1 : 0, owner.id);
|
|
17578
17608
|
if (rebooted) {
|
|
17579
17609
|
await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = 'host reboot confirmed' WHERE owner = ? AND state = 'granted'`, owner.id);
|
|
17580
17610
|
} else if (stopped) {
|
|
@@ -17592,13 +17622,17 @@ class SqlResourceAdmission {
|
|
|
17592
17622
|
}
|
|
17593
17623
|
await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = 'legacy waiter fenced' WHERE owner IS NULL AND state = 'waiting'`);
|
|
17594
17624
|
await tx.run(`UPDATE ${this.permits} SET reason = ? WHERE owner IS NULL AND state = 'granted' AND reason IS NOT ?`, ANONYMOUS_REASON, ANONYMOUS_REASON);
|
|
17595
|
-
await tx.run(`DELETE FROM ${this.permits} WHERE ticket IN (SELECT ticket FROM ${this.permits} WHERE
|
|
17596
|
-
|
|
17625
|
+
await tx.run(`DELETE FROM ${this.permits} WHERE ticket IN (SELECT ticket FROM ${this.permits} r WHERE ${this.prunable}
|
|
17626
|
+
ORDER BY ticket LIMIT ?)`, RELEASED_PERMIT_RETENTION, FENCED_WAITER_REASON, FENCED_WAITER_RETENTION_MS, RELEASED_PERMIT_PRUNE_BATCH);
|
|
17597
17627
|
});
|
|
17598
17628
|
}
|
|
17599
17629
|
get prunableBefore() {
|
|
17600
17630
|
return `(SELECT ticket FROM ${this.permits} WHERE state = 'released' ORDER BY ticket DESC LIMIT 1 OFFSET ?)`;
|
|
17601
17631
|
}
|
|
17632
|
+
get prunable() {
|
|
17633
|
+
return `+r.state = 'released' AND r.ticket <= ${this.prunableBefore} AND (r.reason IS NOT ? OR NOT EXISTS
|
|
17634
|
+
(SELECT 1 FROM ${this.owners} o WHERE o.id = r.owner AND o.expires_at >= ${NOW_MS} - ?))`;
|
|
17635
|
+
}
|
|
17602
17636
|
needsFencing(permit, fence) {
|
|
17603
17637
|
if (permit.owner === null)
|
|
17604
17638
|
return permit.state === "waiting" || permit.reason !== ANONYMOUS_REASON;
|
|
@@ -17623,6 +17657,7 @@ class SqlResourceAdmission {
|
|
|
17623
17657
|
await this.reconcile();
|
|
17624
17658
|
return this.storage.transaction(async (tx) => {
|
|
17625
17659
|
await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = 'worker exit confirmed' WHERE root = ? AND holder = ? AND state IN ('waiting', 'granted') AND (kind = 'session' OR state = 'waiting')`, this.options.root, holder);
|
|
17660
|
+
await tx.run(`UPDATE ${this.permits} SET reason = 'worker exit confirmed' WHERE root = ? AND holder = ? AND state = 'released' AND reason = ?`, this.options.root, holder, FENCED_WAITER_REASON);
|
|
17626
17661
|
await tx.run(`UPDATE ${this.permits} SET reason = ? WHERE root = ? AND holder = ? AND kind = 'process' AND state = 'granted' AND reason IS NOT ?`, UNCONFIRMED_REASON, this.options.root, holder, QUARANTINED_REASON);
|
|
17627
17662
|
const held = await tx.get(`SELECT COUNT(*) AS count FROM ${this.permits} WHERE root = ? AND holder = ? AND kind = 'process' AND state = 'granted'`, this.options.root, holder);
|
|
17628
17663
|
return Number(held?.count);
|
|
@@ -17695,7 +17730,7 @@ async function reapProcessGroups(journal, root, scope) {
|
|
|
17695
17730
|
return { survivors: [], unprobeable: [] };
|
|
17696
17731
|
return settleProcessGroups(storage, root, scope, true);
|
|
17697
17732
|
}
|
|
17698
|
-
var OWNER_LEASE_MS = 30000, REAP_TERM_GRACE_MS = 5000, REAP_KILL_GRACE_MS = 1e4, REAP_POLL_MS = 50, RELEASED_PERMIT_RETENTION = 256, RELEASED_PERMIT_PRUNE_BATCH = 1000, EXPIRED_REASON = "owner expired; cleanup unconfirmed", UNCONFIRMED_REASON = "cleanup unconfirmed", QUARANTINED_REASON = "cleanup unconfirmed; permit quarantined", ANONYMOUS_REASON = "owner identity unavailable; cleanup unconfirmed", QUARANTINED_CAPACITY_WAIT_MS, QuarantinedCapacityError, NOW_MS = "CAST(unixepoch('subsec') * 1000 AS INTEGER)", LATE_ATTEMPT_POLL_MS = 100, LATE_ATTEMPT_REASON = "waiting for its attempt lease", STAGE_START_SPACING_MS = 5000, STAGE_START_PRESSURE_REASON = "paced for host load";
|
|
17733
|
+
var OWNER_LEASE_MS = 30000, REAP_TERM_GRACE_MS = 5000, REAP_KILL_GRACE_MS = 1e4, REAP_POLL_MS = 50, RELEASED_PERMIT_RETENTION = 256, RELEASED_PERMIT_PRUNE_BATCH = 1000, EXPIRED_REASON = "owner expired; cleanup unconfirmed", UNCONFIRMED_REASON = "cleanup unconfirmed", QUARANTINED_REASON = "cleanup unconfirmed; permit quarantined", ANONYMOUS_REASON = "owner identity unavailable; cleanup unconfirmed", QUARANTINED_CAPACITY_WAIT_MS, QuarantinedCapacityError, FENCED_WAITER_REASON = "abandoned waiter fenced", FENCED_WAITER_RETENTION_MS, AdmissionHolderStoppedError, NOW_MS = "CAST(unixepoch('subsec') * 1000 AS INTEGER)", LATE_ATTEMPT_POLL_MS = 100, LATE_ATTEMPT_REASON = "waiting for its attempt lease", STAGE_START_SPACING_MS = 5000, STAGE_START_PRESSURE_REASON = "paced for host load";
|
|
17699
17734
|
var init_resource_admission = __esm(() => {
|
|
17700
17735
|
init_internal();
|
|
17701
17736
|
init_sql_run_journal();
|
|
@@ -17709,6 +17744,12 @@ var init_resource_admission = __esm(() => {
|
|
|
17709
17744
|
super("worker_cleanup_unconfirmed", `Local worker process cleanup unconfirmed; process permit refused after ${Math.round(waitedMs)} ms: ${limits} ${capacity.length === 1 ? "is" : "are"} held by quarantined permits of process groups ${groups} that survived SIGKILL`);
|
|
17710
17745
|
}
|
|
17711
17746
|
};
|
|
17747
|
+
FENCED_WAITER_RETENTION_MS = 10 * 60000;
|
|
17748
|
+
AdmissionHolderStoppedError = class AdmissionHolderStoppedError extends WorkflowInfrastructureError {
|
|
17749
|
+
constructor(reason) {
|
|
17750
|
+
super("stale_work_lease", `Stale resource admission attempt: the admission holder was stopped${reason === undefined ? "" : ` (${reason})`}`);
|
|
17751
|
+
}
|
|
17752
|
+
};
|
|
17712
17753
|
});
|
|
17713
17754
|
|
|
17714
17755
|
// dist/builtin/workflows/src/workers/admission-binding.ts
|
|
@@ -65111,6 +65152,37 @@ var init_workflow_route_owner = __esm(() => {
|
|
|
65111
65152
|
});
|
|
65112
65153
|
|
|
65113
65154
|
// dist/builtin/workflows/src/runs/foreground/sdk-intercom.ts
|
|
65155
|
+
function indexRoots(runs) {
|
|
65156
|
+
const runById = new Map(runs.map((run) => [run.id, run]));
|
|
65157
|
+
const rootOf = new Map;
|
|
65158
|
+
const deliverable = new Set;
|
|
65159
|
+
for (const run of runs) {
|
|
65160
|
+
const root = reciprocalWorkflowRootRunId(runById, run.id);
|
|
65161
|
+
rootOf.set(run.id, root);
|
|
65162
|
+
if (root !== undefined && run.stages.some((stage) => stage.pendingStageDeliveryAvailable === true))
|
|
65163
|
+
deliverable.add(root);
|
|
65164
|
+
}
|
|
65165
|
+
return { rootOf, deliverable };
|
|
65166
|
+
}
|
|
65167
|
+
function createStandaloneRouteOwnership(store, roots) {
|
|
65168
|
+
const activated = new Set;
|
|
65169
|
+
let index;
|
|
65170
|
+
const dispose = store.subscribeInvalidation(() => {
|
|
65171
|
+
index = undefined;
|
|
65172
|
+
});
|
|
65173
|
+
return {
|
|
65174
|
+
owns: (runId) => {
|
|
65175
|
+
index ??= indexRoots(store.runs());
|
|
65176
|
+
const root = index.rootOf.get(runId) ?? runId;
|
|
65177
|
+
if (!roots.has(root))
|
|
65178
|
+
return false;
|
|
65179
|
+
if (!activated.has(root) && index.deliverable.has(root))
|
|
65180
|
+
activated.add(root);
|
|
65181
|
+
return activated.has(root);
|
|
65182
|
+
},
|
|
65183
|
+
dispose
|
|
65184
|
+
};
|
|
65185
|
+
}
|
|
65114
65186
|
function releaseRoot(owner, runId) {
|
|
65115
65187
|
const remaining = (owner.roots.get(runId) ?? 1) - 1;
|
|
65116
65188
|
if (remaining > 0) {
|
|
@@ -65126,22 +65198,14 @@ async function acquireStandaloneIntercom(store, runId, cwd) {
|
|
|
65126
65198
|
if (hasWorkflowPendingStageRouteOwner(store))
|
|
65127
65199
|
return async () => {};
|
|
65128
65200
|
const roots = new Map;
|
|
65129
|
-
const activated = new Set;
|
|
65130
65201
|
owner = {
|
|
65131
65202
|
roots,
|
|
65132
65203
|
ready: outsideWorkflowExecutions(async () => {
|
|
65133
65204
|
await Promise.resolve().then(() => init_workflow_route_owner());
|
|
65134
65205
|
const routes = createWorkflowRouteOwner(cwd);
|
|
65135
|
-
const
|
|
65136
|
-
|
|
65137
|
-
|
|
65138
|
-
if (!roots.has(root))
|
|
65139
|
-
return false;
|
|
65140
|
-
if (!activated.has(root) && runs.some((run) => durableRootRunIdForRun(runs, run.id) === root && run.stages.some((stage) => stage.pendingStageDeliveryAvailable === true)))
|
|
65141
|
-
activated.add(root);
|
|
65142
|
-
return activated.has(root);
|
|
65143
|
-
});
|
|
65144
|
-
return { routes, bridge };
|
|
65206
|
+
const ownership = createStandaloneRouteOwnership(store, roots);
|
|
65207
|
+
const bridge = registerPendingStageIntercomBridge({ events: routes.events }, store, ownership.owns);
|
|
65208
|
+
return { routes, bridge, unwatch: ownership.dispose };
|
|
65145
65209
|
})
|
|
65146
65210
|
};
|
|
65147
65211
|
owners2.set(store, owner);
|
|
@@ -65165,6 +65229,7 @@ async function acquireStandaloneIntercom(store, runId, cwd) {
|
|
|
65165
65229
|
initialized.bridge.refresh();
|
|
65166
65230
|
if (retained.roots.size === 0) {
|
|
65167
65231
|
initialized.bridge();
|
|
65232
|
+
initialized.unwatch();
|
|
65168
65233
|
if (owners2.get(store) === retained)
|
|
65169
65234
|
owners2.delete(store);
|
|
65170
65235
|
}
|
|
@@ -65176,10 +65241,10 @@ async function acquireStandaloneIntercom(store, runId, cwd) {
|
|
|
65176
65241
|
var owners2;
|
|
65177
65242
|
var init_sdk_intercom = __esm(() => {
|
|
65178
65243
|
init_execution_context_boundary();
|
|
65179
|
-
init_run_owner_backend();
|
|
65180
65244
|
init_pending_stage_intercom();
|
|
65181
65245
|
init_intercom_group();
|
|
65182
65246
|
init_pending_stage_route_readiness();
|
|
65247
|
+
init_workflow_run_ownership();
|
|
65183
65248
|
owners2 = new WeakMap;
|
|
65184
65249
|
});
|
|
65185
65250
|
|
|
@@ -82001,7 +82066,7 @@ async function runWorker(start) {
|
|
|
82001
82066
|
try {
|
|
82002
82067
|
return await admission.acquire(kind, signal, requestOptions);
|
|
82003
82068
|
} catch (error) {
|
|
82004
|
-
if (error instanceof QuarantinedCapacityError && !attemptFenced) {
|
|
82069
|
+
if ((error instanceof QuarantinedCapacityError || error instanceof AdmissionHolderStoppedError) && !attemptFenced) {
|
|
82005
82070
|
poolFault = error.message;
|
|
82006
82071
|
attemptFenced = true;
|
|
82007
82072
|
controller.abort(new WorkAttemptFencedError(error.message));
|
|
@@ -12229,15 +12229,12 @@ class JournalDurableBackend {
|
|
|
12229
12229
|
commitCheckpoint(checkpoint, options) {
|
|
12230
12230
|
if (options?.signal?.aborted)
|
|
12231
12231
|
return Promise.reject(new JournalOperationAbortedError(checkpoint.workflowId, options.signal.reason instanceof Error ? options.signal.reason : undefined));
|
|
12232
|
-
|
|
12233
|
-
|
|
12234
|
-
|
|
12235
|
-
|
|
12236
|
-
|
|
12237
|
-
|
|
12238
|
-
yield* this.append(checkpoint.workflowId, { type: RUN_EVENT_TYPES.checkpointRecorded, payload: checkpoint }, {
|
|
12239
|
-
key: JSON.stringify([RUN_EVENT_TYPES.checkpointRecorded, checkpoint.kind, checkpoint.checkpointId])
|
|
12240
|
-
});
|
|
12232
|
+
try {
|
|
12233
|
+
validateCheckpointInvocationFingerprint(checkpoint);
|
|
12234
|
+
} catch (error) {
|
|
12235
|
+
return Promise.reject(error);
|
|
12236
|
+
}
|
|
12237
|
+
return this.queueAppend(checkpoint.workflowId, { type: RUN_EVENT_TYPES.checkpointRecorded, payload: checkpoint }, JSON.stringify([RUN_EVENT_TYPES.checkpointRecorded, checkpoint.kind, checkpoint.checkpointId]), true);
|
|
12241
12238
|
}
|
|
12242
12239
|
async commitAdditiveCheckpointBestEffort(checkpoint) {
|
|
12243
12240
|
try {
|
|
@@ -12374,7 +12371,7 @@ class JournalDurableBackend {
|
|
|
12374
12371
|
payload: { messageId, delivery, ...accounting === undefined ? {} : { accounting } }
|
|
12375
12372
|
});
|
|
12376
12373
|
}
|
|
12377
|
-
queueAppend(runId, event, key) {
|
|
12374
|
+
queueAppend(runId, event, key, requiresRegisteredRun = false) {
|
|
12378
12375
|
try {
|
|
12379
12376
|
this.ensureOpen();
|
|
12380
12377
|
this.assertExecutionActive();
|
|
@@ -12387,6 +12384,7 @@ class JournalDurableBackend {
|
|
|
12387
12384
|
batch.push({
|
|
12388
12385
|
event,
|
|
12389
12386
|
...key === undefined ? {} : { key },
|
|
12387
|
+
...requiresRegisteredRun ? { requiresRegisteredRun } : {},
|
|
12390
12388
|
context: AsyncLocalStorage3.snapshot(),
|
|
12391
12389
|
resolve,
|
|
12392
12390
|
reject
|
|
@@ -12401,9 +12399,12 @@ class JournalDurableBackend {
|
|
|
12401
12399
|
}
|
|
12402
12400
|
return queued;
|
|
12403
12401
|
}
|
|
12404
|
-
*appendBatch(runId,
|
|
12405
|
-
if (this.appendBatches.get(runId) ===
|
|
12402
|
+
*appendBatch(runId, queued) {
|
|
12403
|
+
if (this.appendBatches.get(runId) === queued)
|
|
12406
12404
|
this.appendBatches.delete(runId);
|
|
12405
|
+
const batch = yield* this.registeredAppends(runId, queued);
|
|
12406
|
+
if (batch.length === 0)
|
|
12407
|
+
return;
|
|
12407
12408
|
const appendMany = runJournalAppendMany(this.journal);
|
|
12408
12409
|
if (appendMany === undefined || batch.length === 1) {
|
|
12409
12410
|
for (const append of batch) {
|
|
@@ -12441,6 +12442,25 @@ class JournalDurableBackend {
|
|
|
12441
12442
|
append.reject(failure);
|
|
12442
12443
|
}
|
|
12443
12444
|
}
|
|
12445
|
+
*registeredAppends(runId, batch) {
|
|
12446
|
+
if (!batch.some((append) => append.requiresRegisteredRun === true))
|
|
12447
|
+
return [...batch];
|
|
12448
|
+
let registered;
|
|
12449
|
+
try {
|
|
12450
|
+
registered = (yield* this.current(runId)) !== undefined;
|
|
12451
|
+
} catch (error) {
|
|
12452
|
+
for (const append of batch)
|
|
12453
|
+
if (append.requiresRegisteredRun === true)
|
|
12454
|
+
append.reject(error);
|
|
12455
|
+
return batch.filter((append) => append.requiresRegisteredRun !== true);
|
|
12456
|
+
}
|
|
12457
|
+
if (registered)
|
|
12458
|
+
return [...batch];
|
|
12459
|
+
for (const append of batch)
|
|
12460
|
+
if (append.requiresRegisteredRun === true)
|
|
12461
|
+
append.resolve();
|
|
12462
|
+
return batch.filter((append) => append.requiresRegisteredRun !== true);
|
|
12463
|
+
}
|
|
12444
12464
|
getToolOutput(...args) {
|
|
12445
12465
|
return this.projection.getToolOutput(...args);
|
|
12446
12466
|
}
|
|
@@ -17553,7 +17573,7 @@ class SqlResourceAdmission {
|
|
|
17553
17573
|
signal?.throwIfAborted();
|
|
17554
17574
|
const operation = randomUUID7();
|
|
17555
17575
|
const release = (reason = "resource cleanup confirmed") => this.storage.transaction(async (tx) => {
|
|
17556
|
-
await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = ? WHERE operation = ? AND state != 'released'`, reason, operation);
|
|
17576
|
+
await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = ? WHERE operation = ? AND (state != 'released' OR reason = ?)`, reason, operation, FENCED_WAITER_REASON);
|
|
17557
17577
|
});
|
|
17558
17578
|
release.bindProcess = async (identity) => {
|
|
17559
17579
|
if (kind !== "process" || identity.kind !== "unix-process-group" || !Number.isSafeInteger(identity.id) || identity.id <= 0)
|
|
@@ -17565,6 +17585,15 @@ class SqlResourceAdmission {
|
|
|
17565
17585
|
throw new Error("Resource process permit is no longer held");
|
|
17566
17586
|
});
|
|
17567
17587
|
};
|
|
17588
|
+
const keepWaiting = async (tx) => {
|
|
17589
|
+
const permit = await tx.get(`SELECT state, reason FROM ${this.permits} WHERE operation = ?`, operation);
|
|
17590
|
+
if (permit?.state === "released" && permit.reason === FENCED_WAITER_REASON)
|
|
17591
|
+
await tx.run(`UPDATE ${this.permits} SET state = 'waiting', reason = NULL WHERE operation = ?`, operation);
|
|
17592
|
+
else if (permit === undefined)
|
|
17593
|
+
throw new AdmissionHolderStoppedError("permit no longer recorded");
|
|
17594
|
+
else if (permit.state !== "waiting")
|
|
17595
|
+
throw new AdmissionHolderStoppedError(permit.reason === null ? undefined : String(permit.reason));
|
|
17596
|
+
};
|
|
17568
17597
|
await this.storage.transaction(async (tx) => {
|
|
17569
17598
|
await tx.run(`UPDATE ${this.owners} SET expires_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) + ? WHERE id = ?`, OWNER_LEASE_MS, this.owner);
|
|
17570
17599
|
await tx.run(`INSERT INTO ${this.permits}(operation, root, holder, kind, state, owner, attempt) VALUES (?, ?, ?, ?, 'waiting', ?, ?)`, operation, this.options.root, this.options.holder, kind, this.owner, this.options.attempt ? JSON.stringify({
|
|
@@ -17587,11 +17616,12 @@ class SqlResourceAdmission {
|
|
|
17587
17616
|
const granted = await this.storage.transaction(async (tx) => {
|
|
17588
17617
|
quarantined = undefined;
|
|
17589
17618
|
await tx.run(`UPDATE ${this.owners} SET expires_at = CAST(unixepoch('subsec') * 1000 AS INTEGER) + ? WHERE id = ?`, OWNER_LEASE_MS, this.owner);
|
|
17619
|
+
await keepWaiting(tx);
|
|
17590
17620
|
const { root } = this.options;
|
|
17591
17621
|
if (!await this.assertCurrentAttempt(tx)) {
|
|
17592
17622
|
const waiting = await tx.run(`UPDATE ${this.permits} SET reason = ? WHERE operation = ? AND state = 'waiting'`, LATE_ATTEMPT_REASON, operation);
|
|
17593
17623
|
if (waiting.changes !== 1)
|
|
17594
|
-
throw new
|
|
17624
|
+
throw new AdmissionHolderStoppedError(undefined);
|
|
17595
17625
|
return false;
|
|
17596
17626
|
}
|
|
17597
17627
|
let reason = hostReason;
|
|
@@ -17634,7 +17664,7 @@ class SqlResourceAdmission {
|
|
|
17634
17664
|
const stageStart = kind === "session" && request?.delegated !== true;
|
|
17635
17665
|
const updated = await tx.run(`UPDATE ${this.permits} SET state = ?, reason = ?, granted_at = CASE WHEN ? IS NULL AND ? THEN ${NOW_MS} ELSE NULL END WHERE operation = ? AND state = 'waiting'`, reason ? "waiting" : "granted", reason ?? null, reason ?? null, stageStart ? 1 : 0, operation);
|
|
17636
17666
|
if (updated.changes !== 1)
|
|
17637
|
-
throw new
|
|
17667
|
+
throw new AdmissionHolderStoppedError(undefined);
|
|
17638
17668
|
return !reason;
|
|
17639
17669
|
});
|
|
17640
17670
|
if (granted) {
|
|
@@ -17676,7 +17706,7 @@ class SqlResourceAdmission {
|
|
|
17676
17706
|
WHERE p.state IN ('waiting', 'granted')`);
|
|
17677
17707
|
if (live.some((permit) => this.needsFencing(permit, fence)))
|
|
17678
17708
|
return true;
|
|
17679
|
-
return !!await db.get(`SELECT 1 AS prunable FROM ${this.permits} WHERE
|
|
17709
|
+
return !!await db.get(`SELECT 1 AS prunable FROM ${this.permits} r WHERE ${this.prunable} LIMIT 1`, RELEASED_PERMIT_RETENTION, FENCED_WAITER_REASON, FENCED_WAITER_RETENTION_MS);
|
|
17680
17710
|
});
|
|
17681
17711
|
if (!pending)
|
|
17682
17712
|
return;
|
|
@@ -17685,7 +17715,7 @@ class SqlResourceAdmission {
|
|
|
17685
17715
|
const owners = await tx.all(`SELECT o.* FROM ${this.owners} o WHERE o.id IN (SELECT owner FROM ${this.permits} WHERE state IN ('waiting', 'granted'))`);
|
|
17686
17716
|
for (const owner of owners) {
|
|
17687
17717
|
const { rebooted, stopped } = fence(owner);
|
|
17688
|
-
await tx.run(`UPDATE ${this.permits} SET state = 'released', reason =
|
|
17718
|
+
await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = ? WHERE owner = ? AND state = 'waiting' AND (? OR EXISTS (SELECT 1 FROM ${this.owners} WHERE id = ? AND expires_at < ${NOW_MS}))`, FENCED_WAITER_REASON, owner.id, stopped ? 1 : 0, owner.id);
|
|
17689
17719
|
if (rebooted) {
|
|
17690
17720
|
await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = 'host reboot confirmed' WHERE owner = ? AND state = 'granted'`, owner.id);
|
|
17691
17721
|
} else if (stopped) {
|
|
@@ -17703,13 +17733,17 @@ class SqlResourceAdmission {
|
|
|
17703
17733
|
}
|
|
17704
17734
|
await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = 'legacy waiter fenced' WHERE owner IS NULL AND state = 'waiting'`);
|
|
17705
17735
|
await tx.run(`UPDATE ${this.permits} SET reason = ? WHERE owner IS NULL AND state = 'granted' AND reason IS NOT ?`, ANONYMOUS_REASON, ANONYMOUS_REASON);
|
|
17706
|
-
await tx.run(`DELETE FROM ${this.permits} WHERE ticket IN (SELECT ticket FROM ${this.permits} WHERE
|
|
17707
|
-
|
|
17736
|
+
await tx.run(`DELETE FROM ${this.permits} WHERE ticket IN (SELECT ticket FROM ${this.permits} r WHERE ${this.prunable}
|
|
17737
|
+
ORDER BY ticket LIMIT ?)`, RELEASED_PERMIT_RETENTION, FENCED_WAITER_REASON, FENCED_WAITER_RETENTION_MS, RELEASED_PERMIT_PRUNE_BATCH);
|
|
17708
17738
|
});
|
|
17709
17739
|
}
|
|
17710
17740
|
get prunableBefore() {
|
|
17711
17741
|
return `(SELECT ticket FROM ${this.permits} WHERE state = 'released' ORDER BY ticket DESC LIMIT 1 OFFSET ?)`;
|
|
17712
17742
|
}
|
|
17743
|
+
get prunable() {
|
|
17744
|
+
return `+r.state = 'released' AND r.ticket <= ${this.prunableBefore} AND (r.reason IS NOT ? OR NOT EXISTS
|
|
17745
|
+
(SELECT 1 FROM ${this.owners} o WHERE o.id = r.owner AND o.expires_at >= ${NOW_MS} - ?))`;
|
|
17746
|
+
}
|
|
17713
17747
|
needsFencing(permit, fence) {
|
|
17714
17748
|
if (permit.owner === null)
|
|
17715
17749
|
return permit.state === "waiting" || permit.reason !== ANONYMOUS_REASON;
|
|
@@ -17734,6 +17768,7 @@ class SqlResourceAdmission {
|
|
|
17734
17768
|
await this.reconcile();
|
|
17735
17769
|
return this.storage.transaction(async (tx) => {
|
|
17736
17770
|
await tx.run(`UPDATE ${this.permits} SET state = 'released', reason = 'worker exit confirmed' WHERE root = ? AND holder = ? AND state IN ('waiting', 'granted') AND (kind = 'session' OR state = 'waiting')`, this.options.root, holder);
|
|
17771
|
+
await tx.run(`UPDATE ${this.permits} SET reason = 'worker exit confirmed' WHERE root = ? AND holder = ? AND state = 'released' AND reason = ?`, this.options.root, holder, FENCED_WAITER_REASON);
|
|
17737
17772
|
await tx.run(`UPDATE ${this.permits} SET reason = ? WHERE root = ? AND holder = ? AND kind = 'process' AND state = 'granted' AND reason IS NOT ?`, UNCONFIRMED_REASON, this.options.root, holder, QUARANTINED_REASON);
|
|
17738
17773
|
const held = await tx.get(`SELECT COUNT(*) AS count FROM ${this.permits} WHERE root = ? AND holder = ? AND kind = 'process' AND state = 'granted'`, this.options.root, holder);
|
|
17739
17774
|
return Number(held?.count);
|
|
@@ -17806,7 +17841,7 @@ async function reapProcessGroups(journal, root, scope) {
|
|
|
17806
17841
|
return { survivors: [], unprobeable: [] };
|
|
17807
17842
|
return settleProcessGroups(storage, root, scope, true);
|
|
17808
17843
|
}
|
|
17809
|
-
var OWNER_LEASE_MS = 30000, REAP_TERM_GRACE_MS = 5000, REAP_KILL_GRACE_MS = 1e4, REAP_POLL_MS = 50, RELEASED_PERMIT_RETENTION = 256, RELEASED_PERMIT_PRUNE_BATCH = 1000, EXPIRED_REASON = "owner expired; cleanup unconfirmed", UNCONFIRMED_REASON = "cleanup unconfirmed", QUARANTINED_REASON = "cleanup unconfirmed; permit quarantined", ANONYMOUS_REASON = "owner identity unavailable; cleanup unconfirmed", QUARANTINED_CAPACITY_WAIT_MS, QuarantinedCapacityError, NOW_MS = "CAST(unixepoch('subsec') * 1000 AS INTEGER)", LATE_ATTEMPT_POLL_MS = 100, LATE_ATTEMPT_REASON = "waiting for its attempt lease", STAGE_START_SPACING_MS = 5000, STAGE_START_PRESSURE_REASON = "paced for host load";
|
|
17844
|
+
var OWNER_LEASE_MS = 30000, REAP_TERM_GRACE_MS = 5000, REAP_KILL_GRACE_MS = 1e4, REAP_POLL_MS = 50, RELEASED_PERMIT_RETENTION = 256, RELEASED_PERMIT_PRUNE_BATCH = 1000, EXPIRED_REASON = "owner expired; cleanup unconfirmed", UNCONFIRMED_REASON = "cleanup unconfirmed", QUARANTINED_REASON = "cleanup unconfirmed; permit quarantined", ANONYMOUS_REASON = "owner identity unavailable; cleanup unconfirmed", QUARANTINED_CAPACITY_WAIT_MS, QuarantinedCapacityError, FENCED_WAITER_REASON = "abandoned waiter fenced", FENCED_WAITER_RETENTION_MS, AdmissionHolderStoppedError, NOW_MS = "CAST(unixepoch('subsec') * 1000 AS INTEGER)", LATE_ATTEMPT_POLL_MS = 100, LATE_ATTEMPT_REASON = "waiting for its attempt lease", STAGE_START_SPACING_MS = 5000, STAGE_START_PRESSURE_REASON = "paced for host load";
|
|
17810
17845
|
var init_resource_admission = __esm(() => {
|
|
17811
17846
|
init_internal();
|
|
17812
17847
|
init_sql_run_journal();
|
|
@@ -17820,6 +17855,12 @@ var init_resource_admission = __esm(() => {
|
|
|
17820
17855
|
super("worker_cleanup_unconfirmed", `Local worker process cleanup unconfirmed; process permit refused after ${Math.round(waitedMs)} ms: ${limits} ${capacity.length === 1 ? "is" : "are"} held by quarantined permits of process groups ${groups} that survived SIGKILL`);
|
|
17821
17856
|
}
|
|
17822
17857
|
};
|
|
17858
|
+
FENCED_WAITER_RETENTION_MS = 10 * 60000;
|
|
17859
|
+
AdmissionHolderStoppedError = class AdmissionHolderStoppedError extends WorkflowInfrastructureError {
|
|
17860
|
+
constructor(reason) {
|
|
17861
|
+
super("stale_work_lease", `Stale resource admission attempt: the admission holder was stopped${reason === undefined ? "" : ` (${reason})`}`);
|
|
17862
|
+
}
|
|
17863
|
+
};
|
|
17823
17864
|
});
|
|
17824
17865
|
|
|
17825
17866
|
// dist/builtin/workflows/src/workers/admission-binding.ts
|
|
@@ -65222,6 +65263,37 @@ var init_workflow_route_owner = __esm(() => {
|
|
|
65222
65263
|
});
|
|
65223
65264
|
|
|
65224
65265
|
// dist/builtin/workflows/src/runs/foreground/sdk-intercom.ts
|
|
65266
|
+
function indexRoots(runs) {
|
|
65267
|
+
const runById = new Map(runs.map((run) => [run.id, run]));
|
|
65268
|
+
const rootOf = new Map;
|
|
65269
|
+
const deliverable = new Set;
|
|
65270
|
+
for (const run of runs) {
|
|
65271
|
+
const root = reciprocalWorkflowRootRunId(runById, run.id);
|
|
65272
|
+
rootOf.set(run.id, root);
|
|
65273
|
+
if (root !== undefined && run.stages.some((stage) => stage.pendingStageDeliveryAvailable === true))
|
|
65274
|
+
deliverable.add(root);
|
|
65275
|
+
}
|
|
65276
|
+
return { rootOf, deliverable };
|
|
65277
|
+
}
|
|
65278
|
+
function createStandaloneRouteOwnership(store, roots) {
|
|
65279
|
+
const activated = new Set;
|
|
65280
|
+
let index;
|
|
65281
|
+
const dispose = store.subscribeInvalidation(() => {
|
|
65282
|
+
index = undefined;
|
|
65283
|
+
});
|
|
65284
|
+
return {
|
|
65285
|
+
owns: (runId) => {
|
|
65286
|
+
index ??= indexRoots(store.runs());
|
|
65287
|
+
const root = index.rootOf.get(runId) ?? runId;
|
|
65288
|
+
if (!roots.has(root))
|
|
65289
|
+
return false;
|
|
65290
|
+
if (!activated.has(root) && index.deliverable.has(root))
|
|
65291
|
+
activated.add(root);
|
|
65292
|
+
return activated.has(root);
|
|
65293
|
+
},
|
|
65294
|
+
dispose
|
|
65295
|
+
};
|
|
65296
|
+
}
|
|
65225
65297
|
function releaseRoot(owner, runId) {
|
|
65226
65298
|
const remaining = (owner.roots.get(runId) ?? 1) - 1;
|
|
65227
65299
|
if (remaining > 0) {
|
|
@@ -65237,22 +65309,14 @@ async function acquireStandaloneIntercom(store, runId, cwd) {
|
|
|
65237
65309
|
if (hasWorkflowPendingStageRouteOwner(store))
|
|
65238
65310
|
return async () => {};
|
|
65239
65311
|
const roots = new Map;
|
|
65240
|
-
const activated = new Set;
|
|
65241
65312
|
owner = {
|
|
65242
65313
|
roots,
|
|
65243
65314
|
ready: outsideWorkflowExecutions(async () => {
|
|
65244
65315
|
await Promise.resolve().then(() => init_workflow_route_owner());
|
|
65245
65316
|
const routes = createWorkflowRouteOwner(cwd);
|
|
65246
|
-
const
|
|
65247
|
-
|
|
65248
|
-
|
|
65249
|
-
if (!roots.has(root))
|
|
65250
|
-
return false;
|
|
65251
|
-
if (!activated.has(root) && runs.some((run) => durableRootRunIdForRun(runs, run.id) === root && run.stages.some((stage) => stage.pendingStageDeliveryAvailable === true)))
|
|
65252
|
-
activated.add(root);
|
|
65253
|
-
return activated.has(root);
|
|
65254
|
-
});
|
|
65255
|
-
return { routes, bridge };
|
|
65317
|
+
const ownership = createStandaloneRouteOwnership(store, roots);
|
|
65318
|
+
const bridge = registerPendingStageIntercomBridge({ events: routes.events }, store, ownership.owns);
|
|
65319
|
+
return { routes, bridge, unwatch: ownership.dispose };
|
|
65256
65320
|
})
|
|
65257
65321
|
};
|
|
65258
65322
|
owners2.set(store, owner);
|
|
@@ -65276,6 +65340,7 @@ async function acquireStandaloneIntercom(store, runId, cwd) {
|
|
|
65276
65340
|
initialized.bridge.refresh();
|
|
65277
65341
|
if (retained.roots.size === 0) {
|
|
65278
65342
|
initialized.bridge();
|
|
65343
|
+
initialized.unwatch();
|
|
65279
65344
|
if (owners2.get(store) === retained)
|
|
65280
65345
|
owners2.delete(store);
|
|
65281
65346
|
}
|
|
@@ -65287,10 +65352,10 @@ async function acquireStandaloneIntercom(store, runId, cwd) {
|
|
|
65287
65352
|
var owners2;
|
|
65288
65353
|
var init_sdk_intercom = __esm(() => {
|
|
65289
65354
|
init_execution_context_boundary();
|
|
65290
|
-
init_run_owner_backend();
|
|
65291
65355
|
init_pending_stage_intercom();
|
|
65292
65356
|
init_intercom_group();
|
|
65293
65357
|
init_pending_stage_route_readiness();
|
|
65358
|
+
init_workflow_run_ownership();
|
|
65294
65359
|
owners2 = new WeakMap;
|
|
65295
65360
|
});
|
|
65296
65361
|
|