@bastani/atomic 0.9.60 → 0.9.61-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -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 +144 -65
- package/dist/builtin/workflows/src/extension/local-worker-entry.mjs +163 -66
- package/dist/builtin/workflows/src/index.js +144 -65
- package/dist/builtin/workflows/src/local-worker-entry.mjs +163 -66
- package/dist/builtin/workflows/src/shared/workflow-infrastructure-error.d.ts +6 -0
- package/docs/workflows/operations.md +4 -2
- package/npm-shrinkwrap.json +32 -32
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.61] - 2026-11-02
|
|
6
|
+
|
|
7
|
+
A local worker pool that keeps going when one child or one worker goes wrong.
|
|
8
|
+
|
|
9
|
+
- **One failing child no longer takes down the pool:** a child that fails to dispatch fails alone instead of closing the whole local worker pool, and a child whose result is recorded keeps it whatever happens to the pool afterwards ([#4159](https://github.com/bastani-inc/atomic/pull/4159))
|
|
10
|
+
- **Cleanup trouble is a warning, not a failure:** a worker or tool process group that survives SIGKILL shows as a warning on the root run; the worker is replaced and its child replays on another worker within its three attempts ([#4159](https://github.com/bastani-inc/atomic/pull/4159))
|
|
11
|
+
- **Pool faults replay instead of failing:** a child that hits `stale_work_lease`, `worker_lost` or another pool fault replays from its last checkpoint, and fails with the retryable `worker_retry_limit` only after its third attempt ([#4159](https://github.com/bastani-inc/atomic/pull/4159))
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- One failing child no longer takes down a local worker pool (`workflows.localWorkers`). Before, a single child that failed to dispatch, for example on a transient storage error, closed the whole pool. On a busy machine that close often could not confirm in time that a killed worker had exited, so every other child failed with the retryable `Local worker cleanup was not confirmed`. That included children whose work had already finished, and every child the root started afterwards, so a root that retries pooled children retried them in a tight loop until it gave up. Now that failure fails only that child; the pool closes only when the root ends or the pool itself fails. A child whose result is recorded keeps it whatever happens to the pool afterwards. A worker's exit after SIGKILL is confirmed from the operating system, not from a 5-second timer that a busy coordinator could miss. ([#4159](https://github.com/bastani-inc/atomic/pull/4159))
|
|
16
|
+
- Cleanup that a local worker pool really cannot confirm is no longer any child's failure. This covers a tool process group that survives SIGKILL and a worker that outlives it. Instead it shows as a warning on the root run and is written to that worker's log, which is kept. The worker is replaced, and its unfinished child replays on another worker within its usual three attempts. Process permits of the surviving groups stay quarantined, and closing the pool still reports them. Worker logs of a pool that failed are kept as well. ([#4159](https://github.com/bastani-inc/atomic/pull/4159))
|
|
17
|
+
- A child on a local worker whose run fails on a fault of the pool, such as `stale_work_lease`, `worker_lost`, `storage_unavailable` or `intercom_unavailable`, is replayed from its last checkpoint within its three attempts instead of ending failed with a recoverable fault recorded as final. After the third attempt it fails with the retryable `worker_retry_limit`, naming the fault. ([#4159](https://github.com/bastani-inc/atomic/pull/4159))
|
|
18
|
+
- A replayed child on a local worker whose `ctx.tool` call fails again, as it did on an earlier attempt, no longer fails with `Pool checkpoint identity conflict`. ([#4159](https://github.com/bastani-inc/atomic/pull/4159))
|
|
19
|
+
|
|
20
|
+
## [0.9.61-alpha.1] - 2026-11-02
|
|
21
|
+
|
|
22
|
+
A local worker pool that keeps going when one child or one worker goes wrong.
|
|
23
|
+
|
|
24
|
+
- **One failing child no longer takes down the pool:** a child that fails to dispatch fails alone instead of closing the whole local worker pool, and a child whose result is recorded keeps it whatever happens to the pool afterwards ([#4159](https://github.com/bastani-inc/atomic/pull/4159))
|
|
25
|
+
- **Cleanup trouble is a warning, not a failure:** a worker or tool process group that survives SIGKILL shows as a warning on the root run; the worker is replaced and its child replays on another worker within its three attempts ([#4159](https://github.com/bastani-inc/atomic/pull/4159))
|
|
26
|
+
- **Pool faults replay instead of failing:** a child that hits `stale_work_lease`, `worker_lost` or another pool fault replays from its last checkpoint, and fails with the retryable `worker_retry_limit` only after its third attempt ([#4159](https://github.com/bastani-inc/atomic/pull/4159))
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- One failing child no longer takes down a local worker pool (`workflows.localWorkers`). Before, a single child that failed to dispatch, for example on a transient storage error, closed the whole pool. On a busy machine that close often could not confirm in time that a killed worker had exited, so every other child failed with the retryable `Local worker cleanup was not confirmed`. That included children whose work had already finished, and every child the root started afterwards, so a root that retries pooled children retried them in a tight loop until it gave up. Now that failure fails only that child; the pool closes only when the root ends or the pool itself fails. A child whose result is recorded keeps it whatever happens to the pool afterwards. A worker's exit after SIGKILL is confirmed from the operating system, not from a 5-second timer that a busy coordinator could miss. ([#4159](https://github.com/bastani-inc/atomic/pull/4159))
|
|
31
|
+
- Cleanup that a local worker pool really cannot confirm is no longer any child's failure. This covers a tool process group that survives SIGKILL and a worker that outlives it. Instead it shows as a warning on the root run and is written to that worker's log, which is kept. The worker is replaced, and its unfinished child replays on another worker within its usual three attempts. Process permits of the surviving groups stay quarantined, and closing the pool still reports them. Worker logs of a pool that failed are kept as well. ([#4159](https://github.com/bastani-inc/atomic/pull/4159))
|
|
32
|
+
- A child on a local worker whose run fails on a fault of the pool, such as `stale_work_lease`, `worker_lost`, `storage_unavailable` or `intercom_unavailable`, is replayed from its last checkpoint within its three attempts instead of ending failed with a recoverable fault recorded as final. After the third attempt it fails with the retryable `worker_retry_limit`, naming the fault. ([#4159](https://github.com/bastani-inc/atomic/pull/4159))
|
|
33
|
+
- A replayed child on a local worker whose `ctx.tool` call fails again, as it did on an earlier attempt, no longer fails with `Pool checkpoint identity conflict`. ([#4159](https://github.com/bastani-inc/atomic/pull/4159))
|
|
34
|
+
|
|
5
35
|
## [0.9.60] - 2026-11-01
|
|
6
36
|
|
|
7
37
|
Chats that keep working after a workflow wakes them, steadier subagent model choices, and copying that works inside Rex.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/intercom",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.61-alpha.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
|
|
6
6
|
"contributors": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/subagents",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.61-alpha.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for delegating tasks to subagents with parallel execution. Fork of: https://github.com/nicobailon/pi-subagents",
|
|
6
6
|
"contributors": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/web-access",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.61-alpha.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
|
|
6
6
|
"contributors": [
|
|
@@ -20151,6 +20151,25 @@ function admissionProcessStopped(pid) {
|
|
|
20151
20151
|
return error.code === "ESRCH";
|
|
20152
20152
|
}
|
|
20153
20153
|
}
|
|
20154
|
+
function workerProcessExited(pid, final = false) {
|
|
20155
|
+
if (admissionProcessStopped(pid))
|
|
20156
|
+
return true;
|
|
20157
|
+
if (process.platform === "linux") {
|
|
20158
|
+
try {
|
|
20159
|
+
const stat = readFileSync3(`/proc/${pid}/stat`, "utf8");
|
|
20160
|
+
return stat.slice(stat.lastIndexOf(")") + 2).startsWith("Z");
|
|
20161
|
+
} catch {
|
|
20162
|
+
return admissionProcessStopped(pid);
|
|
20163
|
+
}
|
|
20164
|
+
}
|
|
20165
|
+
if (!final)
|
|
20166
|
+
return false;
|
|
20167
|
+
try {
|
|
20168
|
+
return execFileSync("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8", timeout: 5000 }).trim().startsWith("Z");
|
|
20169
|
+
} catch {
|
|
20170
|
+
return admissionProcessStopped(pid);
|
|
20171
|
+
}
|
|
20172
|
+
}
|
|
20154
20173
|
function scanProcessGroup(group) {
|
|
20155
20174
|
const members = new Map;
|
|
20156
20175
|
if (process.platform === "linux") {
|
|
@@ -62685,17 +62704,26 @@ function isSameCheckpointValue(stored, next) {
|
|
|
62685
62704
|
const hash = (value2) => durableHash(value2);
|
|
62686
62705
|
return hash(stored) === hash(next);
|
|
62687
62706
|
}
|
|
62707
|
+
function withTimesOf(later, recorded) {
|
|
62708
|
+
const both = (key) => typeof recorded[key] === "number" && typeof later[key] === "number";
|
|
62709
|
+
return {
|
|
62710
|
+
...later,
|
|
62711
|
+
...both("startedAt") ? { startedAt: recorded.startedAt } : {},
|
|
62712
|
+
...both("endedAt") ? { endedAt: recorded.endedAt } : {},
|
|
62713
|
+
...both("completedAt") ? { completedAt: recorded.completedAt } : {}
|
|
62714
|
+
};
|
|
62715
|
+
}
|
|
62688
62716
|
function isRecordedCheckpointValue(stored, next) {
|
|
62689
62717
|
const recorded = stored;
|
|
62690
62718
|
if (isSameCheckpointValue(recorded, next))
|
|
62691
62719
|
return true;
|
|
62692
|
-
|
|
62693
|
-
if (typeof completedAt !== "number")
|
|
62720
|
+
if (typeof recorded.completedAt !== "number")
|
|
62694
62721
|
return false;
|
|
62695
62722
|
const later = next;
|
|
62696
|
-
if (startedAt === undefined !== (later.startedAt === undefined))
|
|
62723
|
+
if (recorded.startedAt === undefined !== (later.startedAt === undefined))
|
|
62697
62724
|
return false;
|
|
62698
|
-
|
|
62725
|
+
const retimed = withTimesOf(later, recorded);
|
|
62726
|
+
return isSameCheckpointValue(recorded.topology !== undefined && later.topology !== undefined ? { ...retimed, topology: withTimesOf(later.topology, recorded.topology) } : retimed, recorded);
|
|
62699
62727
|
}
|
|
62700
62728
|
var MIRROR_FRESH_MS = 2000;
|
|
62701
62729
|
var init_pool_remote_journal = __esm(() => {
|
|
@@ -63731,15 +63759,7 @@ class SqlPoolQueue {
|
|
|
63731
63759
|
}
|
|
63732
63760
|
}
|
|
63733
63761
|
function isRecordedCheckpoint(stored, payload) {
|
|
63734
|
-
|
|
63735
|
-
return true;
|
|
63736
|
-
const { completedAt, startedAt } = JSON.parse(stored);
|
|
63737
|
-
if (typeof completedAt !== "number")
|
|
63738
|
-
return false;
|
|
63739
|
-
const next = JSON.parse(payload);
|
|
63740
|
-
if (startedAt === undefined !== (next.startedAt === undefined))
|
|
63741
|
-
return false;
|
|
63742
|
-
return serializeJson({ ...next, completedAt, ...startedAt === undefined ? {} : { startedAt } }) === stored;
|
|
63762
|
+
return stored === payload || isRecordedCheckpointValue(JSON.parse(stored), JSON.parse(payload));
|
|
63743
63763
|
}
|
|
63744
63764
|
function openSqlPoolQueue(journal, remote) {
|
|
63745
63765
|
const storage = sqlPoolStorage(journal);
|
|
@@ -68268,7 +68288,7 @@ var init_startup_timeout = () => {};
|
|
|
68268
68288
|
import { fork } from "node:child_process";
|
|
68269
68289
|
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
68270
68290
|
import { createWriteStream } from "node:fs";
|
|
68271
|
-
import { mkdir as mkdir6, rm as rm3, rmdir } from "node:fs/promises";
|
|
68291
|
+
import { appendFile, mkdir as mkdir6, rm as rm3, rmdir } from "node:fs/promises";
|
|
68272
68292
|
import { dirname as dirname15, join as join30, resolve as resolve14 } from "node:path";
|
|
68273
68293
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
68274
68294
|
function isRootLeaseLost(error) {
|
|
@@ -68460,6 +68480,9 @@ class PoolCoordinator {
|
|
|
68460
68480
|
incarnations = new Map;
|
|
68461
68481
|
readyWorkers = new Set;
|
|
68462
68482
|
logs = new Map;
|
|
68483
|
+
keptLogs = new Set;
|
|
68484
|
+
unconfirmedExits = new WeakSet;
|
|
68485
|
+
failed = false;
|
|
68463
68486
|
cleanups = new Set;
|
|
68464
68487
|
controlAcks = new Map;
|
|
68465
68488
|
lastHeard = new Map;
|
|
@@ -68507,7 +68530,6 @@ class PoolCoordinator {
|
|
|
68507
68530
|
nextRecoveryAt = 0;
|
|
68508
68531
|
cleanupFailure;
|
|
68509
68532
|
held = new Set;
|
|
68510
|
-
quarantined = new Set;
|
|
68511
68533
|
unreaped = [];
|
|
68512
68534
|
retained = new Map;
|
|
68513
68535
|
retainedTransitions = Promise.resolve();
|
|
@@ -68700,18 +68722,15 @@ class PoolCoordinator {
|
|
|
68700
68722
|
try {
|
|
68701
68723
|
const { survivors, unprobeable } = await reapProcessGroups(this.journal, this.root, { attempt: work });
|
|
68702
68724
|
if (survivors.length > 0) {
|
|
68703
|
-
this.quarantined.add(work.id);
|
|
68704
68725
|
this.unreaped.push({ attempt: work });
|
|
68705
|
-
this.
|
|
68706
|
-
return;
|
|
68726
|
+
this.reportHealth(undefined, `${unconfirmedCleanup(survivors).message}; child ${work.childRunId} of an earlier incarnation replays`);
|
|
68707
68727
|
}
|
|
68708
68728
|
if (unprobeable.length === 0)
|
|
68709
68729
|
await this.releaseRetained(work);
|
|
68710
68730
|
} catch (error) {
|
|
68711
68731
|
this.cleanupFailure ??= error instanceof Error ? error : new Error(String(error));
|
|
68712
68732
|
} finally {
|
|
68713
|
-
|
|
68714
|
-
this.held.delete(work.id);
|
|
68733
|
+
this.held.delete(work.id);
|
|
68715
68734
|
}
|
|
68716
68735
|
}
|
|
68717
68736
|
releaseRetained(work) {
|
|
@@ -68842,6 +68861,7 @@ class PoolCoordinator {
|
|
|
68842
68861
|
});
|
|
68843
68862
|
}
|
|
68844
68863
|
fail(error) {
|
|
68864
|
+
this.failed = true;
|
|
68845
68865
|
this.port.send({ kind: "failure", error: error.message });
|
|
68846
68866
|
}
|
|
68847
68867
|
failWork(work, error) {
|
|
@@ -69250,7 +69270,11 @@ class PoolCoordinator {
|
|
|
69250
69270
|
terminate();
|
|
69251
69271
|
});
|
|
69252
69272
|
const closed = new Promise((resolve) => child.once("close", () => resolve()));
|
|
69253
|
-
|
|
69273
|
+
let retired = false;
|
|
69274
|
+
const retire = () => {
|
|
69275
|
+
if (retired)
|
|
69276
|
+
return;
|
|
69277
|
+
retired = true;
|
|
69254
69278
|
Promise.race([closed, delay2(STDERR_DRAIN_GRACE_MS)]).then(() => reject(diagnosed(timedOut ? startupTimeoutMessage(startupTimeoutMs) : "Local worker exited before readiness")));
|
|
69255
69279
|
this.children.delete(child);
|
|
69256
69280
|
this.logs.delete(child);
|
|
@@ -69274,7 +69298,7 @@ class PoolCoordinator {
|
|
|
69274
69298
|
const held = slot.work;
|
|
69275
69299
|
if (held)
|
|
69276
69300
|
this.held.add(held.id);
|
|
69277
|
-
let
|
|
69301
|
+
let unconfirmed;
|
|
69278
69302
|
const cleanup = disconnected.then(() => serial).then(async () => {
|
|
69279
69303
|
try {
|
|
69280
69304
|
const admission = this.admission;
|
|
@@ -69287,18 +69311,13 @@ class PoolCoordinator {
|
|
|
69287
69311
|
throw unconfirmedCleanup(alive);
|
|
69288
69312
|
if (alive.length === 0)
|
|
69289
69313
|
return;
|
|
69290
|
-
|
|
69291
|
-
|
|
69292
|
-
this.quarantined.add(held.id);
|
|
69293
|
-
slot.work = undefined;
|
|
69294
|
-
childFailed = true;
|
|
69295
|
-
this.port.send({ kind: "work-failure", workId: held.id, error: error.message });
|
|
69296
|
-
}
|
|
69314
|
+
unconfirmed = unconfirmedCleanup(alive);
|
|
69315
|
+
this.reportHealth(logPath, `${unconfirmed.message}; the worker was retired and replaced`);
|
|
69297
69316
|
} catch (error) {
|
|
69298
69317
|
this.cleanupFailure = error instanceof Error ? error : new Error(String(error));
|
|
69299
69318
|
throw this.cleanupFailure;
|
|
69300
69319
|
} finally {
|
|
69301
|
-
if (held
|
|
69320
|
+
if (held)
|
|
69302
69321
|
this.held.delete(held.id);
|
|
69303
69322
|
}
|
|
69304
69323
|
}).then(async () => {
|
|
@@ -69319,8 +69338,8 @@ class PoolCoordinator {
|
|
|
69319
69338
|
const settled = attempt !== undefined && await this.whileRootLate(queue, () => exhausted ? queue.failExited(lease, attempt) : queue.releaseExited(lease, attempt));
|
|
69320
69339
|
const superseded = work !== undefined && !settled && await this.superseded(work);
|
|
69321
69340
|
if (work !== undefined && exhausted && !superseded)
|
|
69322
|
-
this.failWork(work, diagnosed(childRetryLimit(work, "lost its worker")));
|
|
69323
|
-
if (work === undefined &&
|
|
69341
|
+
this.failWork(work, diagnosed(`${childRetryLimit(work, "lost its worker")}${unconfirmed === undefined ? "" : `; ${unconfirmed.message}`}`));
|
|
69342
|
+
if (work === undefined && ++this.replacements > 3)
|
|
69324
69343
|
this.fail(diagnosed("Local worker retry limit"));
|
|
69325
69344
|
else
|
|
69326
69345
|
await this.spawn();
|
|
@@ -69329,7 +69348,9 @@ class PoolCoordinator {
|
|
|
69329
69348
|
});
|
|
69330
69349
|
this.cleanups.add(cleanup);
|
|
69331
69350
|
cleanup.finally(() => this.cleanups.delete(cleanup));
|
|
69332
|
-
}
|
|
69351
|
+
};
|
|
69352
|
+
slot.retire = retire;
|
|
69353
|
+
child.once("exit", retire);
|
|
69333
69354
|
child.on("message", (message) => {
|
|
69334
69355
|
this.observeResponsiveness();
|
|
69335
69356
|
this.lastHeard.set(worker, { at: performance.now(), stalled: this.stalledMs });
|
|
@@ -69403,10 +69424,11 @@ class PoolCoordinator {
|
|
|
69403
69424
|
const fenced = slot.work;
|
|
69404
69425
|
slot.work = undefined;
|
|
69405
69426
|
if (fenced && !await this.superseded(fenced)) {
|
|
69427
|
+
this.fence(fenced);
|
|
69406
69428
|
if (this.attempts(fenced) < MAX_CHILD_ATTEMPTS)
|
|
69407
69429
|
await leased(() => queue.releaseExited(lease, fenced));
|
|
69408
69430
|
else if (await leased(() => queue.failExited(lease, fenced)))
|
|
69409
|
-
this.failWork(fenced, new Error(childRetryLimit(fenced, "was refused by the root as stale on every attempt")));
|
|
69431
|
+
this.failWork(fenced, new Error(childRetryLimit(fenced, message.fault === undefined ? "was refused by the root as stale on every attempt" : `failed on a fault of the pool on every attempt (last: ${message.fault})`)));
|
|
69410
69432
|
}
|
|
69411
69433
|
return;
|
|
69412
69434
|
}
|
|
@@ -69567,8 +69589,6 @@ class PoolCoordinator {
|
|
|
69567
69589
|
return;
|
|
69568
69590
|
}
|
|
69569
69591
|
for (const work of expired) {
|
|
69570
|
-
if (this.quarantined.has(work.id))
|
|
69571
|
-
continue;
|
|
69572
69592
|
if (this.held.has(work.id)) {
|
|
69573
69593
|
await queue.extend(lease, work, leaseMs).catch((error) => {
|
|
69574
69594
|
if (error.message !== STALE_WORK_LEASE)
|
|
@@ -69584,9 +69604,9 @@ class PoolCoordinator {
|
|
|
69584
69604
|
});
|
|
69585
69605
|
continue;
|
|
69586
69606
|
}
|
|
69587
|
-
if (child && !await this.stop(child)
|
|
69588
|
-
|
|
69589
|
-
if (this.held.has(work.id)
|
|
69607
|
+
if (child && !await this.stop(child))
|
|
69608
|
+
this.retireUnconfirmed(child);
|
|
69609
|
+
if (this.held.has(work.id))
|
|
69590
69610
|
continue;
|
|
69591
69611
|
if ((await unconfirmedProcessGroups(this.journal, root, { attempt: work })).length > 0)
|
|
69592
69612
|
continue;
|
|
@@ -69613,17 +69633,49 @@ class PoolCoordinator {
|
|
|
69613
69633
|
const heard = this.lastHeard.get(worker);
|
|
69614
69634
|
return child.exitCode === null && child.signalCode === null && child.connected && heard !== undefined && performance.now() - heard.at - (this.stalledMs - heard.stalled) < this.options.leaseMs;
|
|
69615
69635
|
}
|
|
69616
|
-
stop(child) {
|
|
69617
|
-
|
|
69618
|
-
|
|
69619
|
-
|
|
69620
|
-
|
|
69621
|
-
|
|
69622
|
-
|
|
69623
|
-
|
|
69624
|
-
|
|
69625
|
-
|
|
69626
|
-
|
|
69636
|
+
async stop(child) {
|
|
69637
|
+
const pid = child.pid;
|
|
69638
|
+
const exited = () => child.exitCode !== null || child.signalCode !== null;
|
|
69639
|
+
if (pid === undefined || exited())
|
|
69640
|
+
return true;
|
|
69641
|
+
const exit = new Promise((resolve) => child.once("exit", () => resolve()));
|
|
69642
|
+
const confirmed = async () => {
|
|
69643
|
+
await Promise.race([exit, delay2(WORKER_EXIT_CONFIRMATION_MS)]);
|
|
69644
|
+
return true;
|
|
69645
|
+
};
|
|
69646
|
+
this.observeResponsiveness();
|
|
69647
|
+
child.kill("SIGKILL");
|
|
69648
|
+
const since = performance.now();
|
|
69649
|
+
const stalledAt = this.stalledMs;
|
|
69650
|
+
for (;; ) {
|
|
69651
|
+
await Promise.race([exit, delay2(WORKER_EXIT_POLL_MS)]);
|
|
69652
|
+
if (exited())
|
|
69653
|
+
return true;
|
|
69654
|
+
if (workerProcessExited(pid))
|
|
69655
|
+
return confirmed();
|
|
69656
|
+
this.observeResponsiveness();
|
|
69657
|
+
if (performance.now() - since - (this.stalledMs - stalledAt) >= WORKER_EXIT_CONFIRMATION_MS)
|
|
69658
|
+
return exited() || workerProcessExited(pid, true) && confirmed();
|
|
69659
|
+
}
|
|
69660
|
+
}
|
|
69661
|
+
retireUnconfirmed(child) {
|
|
69662
|
+
if (this.unconfirmedExits.has(child))
|
|
69663
|
+
return;
|
|
69664
|
+
this.unconfirmedExits.add(child);
|
|
69665
|
+
this.reportHealth(this.logs.get(child), `Local worker cleanup was not confirmed: worker process ${child.pid} was still running ${WORKER_EXIT_CONFIRMATION_MS} ms after SIGKILL; it was retired and replaced`);
|
|
69666
|
+
if (child.connected)
|
|
69667
|
+
child.disconnect();
|
|
69668
|
+
for (const slot of this.slots)
|
|
69669
|
+
if (slot.child === child)
|
|
69670
|
+
slot.retire?.();
|
|
69671
|
+
}
|
|
69672
|
+
reportHealth(log, message) {
|
|
69673
|
+
if (log !== undefined) {
|
|
69674
|
+
this.keptLogs.add(log);
|
|
69675
|
+
appendFile(log, `[atomic ${new Date().toISOString()}] ${message}
|
|
69676
|
+
`).catch(() => {});
|
|
69677
|
+
}
|
|
69678
|
+
this.port.send({ kind: "health", message: log === undefined ? message : `${message} (worker log: ${log})` });
|
|
69627
69679
|
}
|
|
69628
69680
|
async close() {
|
|
69629
69681
|
this.closed = true;
|
|
@@ -69633,10 +69685,15 @@ class PoolCoordinator {
|
|
|
69633
69685
|
this.rateLimitOutbox.close();
|
|
69634
69686
|
let failure;
|
|
69635
69687
|
let stopping = [];
|
|
69688
|
+
let confirmed = [];
|
|
69636
69689
|
try {
|
|
69637
69690
|
await this.recovering?.catch(() => {});
|
|
69638
69691
|
stopping = [...this.children].map((child) => ({ child, log: this.logs.get(child) }));
|
|
69639
|
-
await Promise.all(stopping.map(({ child }) => this.stop(child)));
|
|
69692
|
+
confirmed = await Promise.all(stopping.map(({ child }) => this.stop(child)));
|
|
69693
|
+
stopping.forEach(({ child }, index) => {
|
|
69694
|
+
if (!confirmed[index])
|
|
69695
|
+
this.retireUnconfirmed(child);
|
|
69696
|
+
});
|
|
69640
69697
|
await Promise.all([...this.cleanups]);
|
|
69641
69698
|
if (this.cleanupFailure)
|
|
69642
69699
|
throw this.cleanupFailure;
|
|
@@ -69648,15 +69705,15 @@ class PoolCoordinator {
|
|
|
69648
69705
|
if (alive.length > 0)
|
|
69649
69706
|
throw unconfirmedCleanup(alive);
|
|
69650
69707
|
if (this.desiredControl !== "quit" && this.queue !== undefined && this.lease !== undefined)
|
|
69651
|
-
await this.queue.releaseClosed(this.lease
|
|
69708
|
+
await this.queue.releaseClosed(this.lease).catch((error) => {
|
|
69652
69709
|
if (!(error instanceof StaleLeaseError))
|
|
69653
69710
|
throw error;
|
|
69654
69711
|
});
|
|
69655
69712
|
} catch (error) {
|
|
69656
69713
|
failure = error;
|
|
69657
69714
|
}
|
|
69658
|
-
if (failure === undefined) {
|
|
69659
|
-
const stopped = stopping.flatMap(({
|
|
69715
|
+
if (failure === undefined && !this.failed) {
|
|
69716
|
+
const stopped = stopping.flatMap(({ log }, index) => log !== undefined && !this.keptLogs.has(log) && confirmed[index] === true ? [log] : []);
|
|
69660
69717
|
await Promise.all(stopped.map((log) => rm3(log, { force: true }).catch(() => {})));
|
|
69661
69718
|
if (this.logDirectory !== undefined)
|
|
69662
69719
|
await rmdir(this.logDirectory).catch(() => {});
|
|
@@ -69682,7 +69739,7 @@ function childRetryLimit(work, reason) {
|
|
|
69682
69739
|
const exportName = work.definition.slice(work.definition.lastIndexOf("#") + 1);
|
|
69683
69740
|
return `Local worker retry limit: child ${work.childRunId} (${exportName}) ${reason} ${MAX_CHILD_ATTEMPTS} times`;
|
|
69684
69741
|
}
|
|
69685
|
-
var CONTROL_ACKNOWLEDGEMENT_STALL_MS = 5000, RECOVERY_INTERVAL_MS = 500, TICK_INTERVAL_MS = 100, STALL_THRESHOLD_MS = 500, COORDINATOR_LEASE_MS, LATE_ROOT_POLL_MS, MIRROR_INTERVAL_MS = 1e4, STDERR_TAIL_CHARACTERS = 8192, STDERR_DRAIN_GRACE_MS = 1000, MAX_CHILD_ATTEMPTS = 3, ROUTE_REGISTRATION_WAIT_MS = 120000, STAGE_WATCH_BACKLOG_EVENTS = 2000, STAGE_WATCH_BACKLOG_BYTES, PENDING_STAGE_MESSAGE_EVENT2 = "atomic:workflow-pending-stage-message", STICKY_LIVE_DELIVERED_EVENT2 = "atomic:workflow-sticky-live-delivered", PENDING_STAGE_UNDELIVERABLE_EVENT2 = "atomic:workflow-pending-stage-undeliverable", ENDED_WATCHES_REMEMBERED = 1024;
|
|
69742
|
+
var CONTROL_ACKNOWLEDGEMENT_STALL_MS = 5000, RECOVERY_INTERVAL_MS = 500, TICK_INTERVAL_MS = 100, STALL_THRESHOLD_MS = 500, COORDINATOR_LEASE_MS, LATE_ROOT_POLL_MS, MIRROR_INTERVAL_MS = 1e4, STDERR_TAIL_CHARACTERS = 8192, STDERR_DRAIN_GRACE_MS = 1000, WORKER_EXIT_CONFIRMATION_MS = 5000, WORKER_EXIT_POLL_MS = 50, MAX_CHILD_ATTEMPTS = 3, ROUTE_REGISTRATION_WAIT_MS = 120000, STAGE_WATCH_BACKLOG_EVENTS = 2000, STAGE_WATCH_BACKLOG_BYTES, PENDING_STAGE_MESSAGE_EVENT2 = "atomic:workflow-pending-stage-message", STICKY_LIVE_DELIVERED_EVENT2 = "atomic:workflow-sticky-live-delivered", PENDING_STAGE_UNDELIVERABLE_EVENT2 = "atomic:workflow-pending-stage-undeliverable", ENDED_WATCHES_REMEMBERED = 1024;
|
|
69686
69743
|
var init_pool_coordinator = __esm(() => {
|
|
69687
69744
|
init_internal();
|
|
69688
69745
|
init_pool_remote_journal();
|
|
@@ -69697,6 +69754,7 @@ var init_pool_coordinator = __esm(() => {
|
|
|
69697
69754
|
init_pending_stage_route_readiness();
|
|
69698
69755
|
init_store();
|
|
69699
69756
|
init_workflow_infrastructure_error();
|
|
69757
|
+
init_admission_host();
|
|
69700
69758
|
init_pool_journal();
|
|
69701
69759
|
init_pooled_watch_trace();
|
|
69702
69760
|
init_resource_admission();
|
|
@@ -71562,6 +71620,7 @@ class LocalWorkerPool {
|
|
|
71562
71620
|
works = new Map;
|
|
71563
71621
|
attempts = new Map;
|
|
71564
71622
|
results = new Map;
|
|
71623
|
+
healthEvents = [];
|
|
71565
71624
|
workFailures = new Map;
|
|
71566
71625
|
changed = Promise.withResolvers();
|
|
71567
71626
|
waiting = 0;
|
|
@@ -71619,6 +71678,9 @@ class LocalWorkerPool {
|
|
|
71619
71678
|
if (options.admission && options.rootId === undefined)
|
|
71620
71679
|
workflowResourceAdmission.set(backend, options.admission);
|
|
71621
71680
|
}
|
|
71681
|
+
get health() {
|
|
71682
|
+
return this.healthEvents;
|
|
71683
|
+
}
|
|
71622
71684
|
get workerPids() {
|
|
71623
71685
|
return [...this.pids];
|
|
71624
71686
|
}
|
|
@@ -72212,14 +72274,8 @@ class LocalWorkerPool {
|
|
|
72212
72274
|
try {
|
|
72213
72275
|
return await this.dispatch(definition, inputs, options);
|
|
72214
72276
|
} catch (error) {
|
|
72215
|
-
if (
|
|
72216
|
-
|
|
72217
|
-
try {
|
|
72218
|
-
if (this.desiredControl !== "quit" || !findWorkflowGracefulQuit(error))
|
|
72219
|
-
await this.shutdown();
|
|
72220
|
-
} catch (closeError) {
|
|
72221
|
-
throw asWorkflowInfrastructureError(closeError) ?? closeError;
|
|
72222
|
-
}
|
|
72277
|
+
if (this.failure !== undefined || this.coordinatorLost !== undefined)
|
|
72278
|
+
await this.shutdown().catch(() => {});
|
|
72223
72279
|
throw asWorkflowInfrastructureError(error) ?? error;
|
|
72224
72280
|
}
|
|
72225
72281
|
}
|
|
@@ -72287,8 +72343,13 @@ class LocalWorkerPool {
|
|
|
72287
72343
|
try {
|
|
72288
72344
|
for (;; ) {
|
|
72289
72345
|
options.signal?.throwIfAborted();
|
|
72290
|
-
if (this.closed)
|
|
72291
|
-
|
|
72346
|
+
if (!this.results.has(work.id) && (this.closed || this.failure !== undefined)) {
|
|
72347
|
+
const recorded = await this.queue?.result(root, work.id).catch(() => {
|
|
72348
|
+
return;
|
|
72349
|
+
});
|
|
72350
|
+
if (recorded !== undefined)
|
|
72351
|
+
this.results.set(work.id, recorded);
|
|
72352
|
+
}
|
|
72292
72353
|
const result = this.results.get(work.id);
|
|
72293
72354
|
if (result) {
|
|
72294
72355
|
this.completing++;
|
|
@@ -72312,6 +72373,8 @@ class LocalWorkerPool {
|
|
|
72312
72373
|
this.endFailedChild(work, failed);
|
|
72313
72374
|
throw failed;
|
|
72314
72375
|
}
|
|
72376
|
+
if (this.closed)
|
|
72377
|
+
throw this.closedError(root);
|
|
72315
72378
|
if (this.failure)
|
|
72316
72379
|
throw this.failure;
|
|
72317
72380
|
if (this.budgetBlocked)
|
|
@@ -72346,6 +72409,19 @@ class LocalWorkerPool {
|
|
|
72346
72409
|
if (this.reconnectingViews.size > 0 || this.outboxes.size > 0)
|
|
72347
72410
|
this.reconnectStageViews();
|
|
72348
72411
|
}
|
|
72412
|
+
recordHealth(message) {
|
|
72413
|
+
this.healthEvents.push(message);
|
|
72414
|
+
const root = this.root;
|
|
72415
|
+
if (root === undefined || this.store === undefined)
|
|
72416
|
+
return;
|
|
72417
|
+
this.store.recordNotice({
|
|
72418
|
+
id: `local-worker-health:${root}:${this.healthEvents.length}`,
|
|
72419
|
+
runId: root,
|
|
72420
|
+
level: "warning",
|
|
72421
|
+
message: `Local worker pool: ${message}`,
|
|
72422
|
+
createdAt: Date.now()
|
|
72423
|
+
});
|
|
72424
|
+
}
|
|
72349
72425
|
evaluateBudget() {
|
|
72350
72426
|
const decision = this.rootBudget?.checkpoint();
|
|
72351
72427
|
const blocked = decision?.kind === "exhausted" || decision?.kind === "wrap_up";
|
|
@@ -72464,6 +72540,9 @@ class LocalWorkerPool {
|
|
|
72464
72540
|
this.failure ??= new Error(event.error);
|
|
72465
72541
|
this.signalChange();
|
|
72466
72542
|
return;
|
|
72543
|
+
case "health":
|
|
72544
|
+
this.recordHealth(event.message);
|
|
72545
|
+
return;
|
|
72467
72546
|
case "stage-events":
|
|
72468
72547
|
this.stageWatches.get(event.watch)?.({ kind: "events", events: event.events });
|
|
72469
72548
|
this.notice({ kind: "stage-events-applied", watch: event.watch });
|