Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions src/storage/worker-lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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). */
Expand Down Expand Up @@ -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`);
Expand Down
10 changes: 9 additions & 1 deletion tests/claude-dotenv-provenance-transport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Give the Bun child a contention-safe deadline

On contended cross-platform runners, this 3-second ceiling can turn a healthy cold Bun launch into ETIMEDOUT, and line 46 then fails all three transport tests before they exercise the behavior. The Windows CI notes in tests/codex-catalog-restore.test.ts:41-42 record the same spawnSync(bun ...) pattern taking about 5.4 seconds under load, while tests/helpers/test-budget.ts:33-34 assigns real child processes a 45-second budget. Keep the subprocess bounded, but use a deadline with CI-contention headroom (and an appropriate outer test budget) rather than 3 seconds.

Useful? React with 👍 / 👎.

killSignal: "SIGKILL",
});
if (result.error) throw result.error;
expect(result.status).toBe(0);
return JSON.parse(result.stdout) as {
context: { anthropicEnvSlots: string[] } | null;
Expand Down
9 changes: 9 additions & 0 deletions tests/storage-worker-os-join-settle.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
Loading