From 3b1d5c3c9c32204538227784c887c317ffa3da12 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Fri, 7 Aug 2026 07:25:03 +0200 Subject: [PATCH 1/3] fix(storage): settle worker teardown on Linux --- src/storage/worker-lifecycle.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/storage/worker-lifecycle.ts b/src/storage/worker-lifecycle.ts index bed4946f6d..846af73555 100644 --- a/src/storage/worker-lifecycle.ts +++ b/src/storage/worker-lifecycle.ts @@ -11,10 +11,9 @@ * time (so we cannot miss `self.close()` / early exit), stays in `liveWorkers` * until that close settles, and `drainStorageWorkers()` joins every in-flight * terminate. Spawns are serialized through `withStorageWorkerSpawnGate` so the - * next Worker cannot be created until prior threads have exited. On Windows and - * macOS, a post-close settle covers the OS join gap Bun does not expose - * (Windows unbalanced join panic; macOS Silicon balanced-count segfault under - * `bun test --isolate`). + * next Worker cannot be created until prior threads have exited. A post-close + * settle covers the OS/runtime join gap Bun does not expose on Windows, macOS, + * and Linux (including Bun 1.3.14 isolate crashes and Linux `epoll_ctl` reuse). */ import { createAdmissionGate, type AdmissionMetrics, type AdmissionReservation } from "../lib/admission"; @@ -52,15 +51,15 @@ let spawnCancelEpoch = 0; /** * OS-join gap after the `close` event on platforms where Bun's Worker reclaim * races the isolate/file boundary (not a CI job-timeout bump). - * Windows GHA at 250ms and 750ms still left `workers_spawned(N) - * workers_terminated(N-1)` panics under isolate (seen mid - * `storage-mutation-race` with 11/10). 1500ms covers deferred reclaim under - * stacked policy/restore workers. Darwin uses 250ms. + * Windows GHA at 250ms and 750ms still left `workers_spawned(N)` + * `workers_terminated(N-1)` panics under isolate, so Windows keeps 1500ms. + * Darwin and Linux use a shorter settle for the balanced-count/epoll reclaim + * window seen on Bun 1.3.14. */ -const WORKER_OS_JOIN_MS = process.platform === "win32" ? 1_500 : 250; - -function needsWorkerOsJoinSettle(): boolean { - return process.platform === "win32" || process.platform === "darwin"; +export function storageWorkerOsJoinSettleMs(platform = process.platform): number { + if (platform === "win32") return 1_500; + if (platform === "darwin" || platform === "linux") return 250; + return 0; } /** Invalidate spawn callbacks still waiting on the gate (reset / server drain). */ @@ -181,9 +180,10 @@ export function terminateStorageWorker(worker: Worker, timeoutMs = 5_000): Promi // only forces `closed`, it does not prove the OS thread has exited. // Callers that catch and continue (e.g. drainAndShutdown) still need // that gap before the next isolate reclaim or server.stop. - if (needsWorkerOsJoinSettle()) { + const settleMs = storageWorkerOsJoinSettleMs(); + if (settleMs > 0) { await Bun.sleep(0); - await Bun.sleep(WORKER_OS_JOIN_MS); + await Bun.sleep(settleMs); } if (timedOut) { throw new Error(`storage worker did not exit within ${timeoutMs}ms`); From 6957c72d7363a7683c06e16b3a2fdadb605cc5f1 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Fri, 7 Aug 2026 07:25:26 +0200 Subject: [PATCH 2/3] test(claude): bound launcher probe subprocesses --- tests/claude-dotenv-provenance-transport.test.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/claude-dotenv-provenance-transport.test.ts b/tests/claude-dotenv-provenance-transport.test.ts index 8373732b6e..d048f75598 100644 --- a/tests/claude-dotenv-provenance-transport.test.ts +++ b/tests/claude-dotenv-provenance-transport.test.ts @@ -5,6 +5,8 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { pathToFileURL } from "node:url"; +const PROBE_TIMEOUT_MS = 3_000; + /** * Project dotenv can write environment variables before OpenCodex evaluates, * but it cannot add the random proof argument emitted by the plain-Node npm @@ -35,7 +37,13 @@ describe("Node launcher context transport", () => { delete env.OCX_PRE_BUN_ANTHROPIC_ENV; if (contextEnv === undefined) delete env.OCX_NODE_LAUNCH_CONTEXT; else env.OCX_NODE_LAUNCH_CONTEXT = contextEnv; - const result = spawnSync(process.execPath, [probe, ...args], { encoding: "utf8", env }); + const result = spawnSync(process.execPath, [probe, ...args], { + encoding: "utf8", + env, + timeout: PROBE_TIMEOUT_MS, + killSignal: "SIGKILL", + }); + if (result.error) throw result.error; expect(result.status).toBe(0); return JSON.parse(result.stdout) as { context: { anthropicEnvSlots: string[] } | null; From a53163f0b1ba72b23ed5c2d8efc0e51661e47977 Mon Sep 17 00:00:00 2001 From: Wibias <37517432+Wibias@users.noreply.github.com> Date: Fri, 7 Aug 2026 07:25:34 +0200 Subject: [PATCH 3/3] test(storage): cover Linux worker join settle --- tests/storage-worker-os-join-settle.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tests/storage-worker-os-join-settle.test.ts diff --git a/tests/storage-worker-os-join-settle.test.ts b/tests/storage-worker-os-join-settle.test.ts new file mode 100644 index 0000000000..6bd92a0aa7 --- /dev/null +++ b/tests/storage-worker-os-join-settle.test.ts @@ -0,0 +1,9 @@ +import { expect, test } from "bun:test"; +import { storageWorkerOsJoinSettleMs } from "../src/storage/worker-lifecycle"; + +test("storage worker OS-join settle covers every Bun 1.3.14 isolate platform", () => { + expect(storageWorkerOsJoinSettleMs("win32")).toBe(1_500); + expect(storageWorkerOsJoinSettleMs("darwin")).toBe(250); + expect(storageWorkerOsJoinSettleMs("linux")).toBe(250); + expect(storageWorkerOsJoinSettleMs("freebsd")).toBe(0); +});