From a4d040d30f2cb30fffbd841b3f0a7052158e2e21 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Tue, 13 Oct 2026 10:05:19 +0000 Subject: [PATCH] fix(chord): a forked child host no longer inherits its parent's CHORD2_LOCK forkDriver spread process.env into the child, so a child host that forks its own children (or a test run inside a p2 worker) passed its lock on, and children without a lock of their own failed with "locked". --- packages/chord/src/manager.ts | 11 ++++++----- packages/chord/test/manager.test.ts | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/chord/src/manager.ts b/packages/chord/src/manager.ts index d8146e683..ae185bf75 100644 --- a/packages/chord/src/manager.ts +++ b/packages/chord/src/manager.ts @@ -161,13 +161,14 @@ const LOCK_ENV = "CHORD2_LOCK"; export function forkDriver(options: ForkDriverOptions): Driver { return { spawn() { + // A lock this process inherited (e.g. as a child host itself) is not the child's: it gets only its own. + const env: NodeJS.ProcessEnv = { ...process.env }; + delete env[LOCK_ENV]; + Object.assign(env, options.env); + if (options.lock !== undefined) env[LOCK_ENV] = options.lock; // Detached: the child leads a process group, so the whole tree can be killed. const child: ChildProcess = fork(options.entry, [...(options.args ?? [])], { - env: { - ...process.env, - ...options.env, - ...(options.lock === undefined ? {} : { [LOCK_ENV]: options.lock }), - }, + env, execArgv: [...(options.execArgv ?? [])], detached: true, stdio: ["ignore", options.onOutput ? "pipe" : "inherit", options.onOutput ? "pipe" : "inherit", "ipc"], diff --git a/packages/chord/test/manager.test.ts b/packages/chord/test/manager.test.ts index 4c2fb6aaf..7281ff720 100644 --- a/packages/chord/test/manager.test.ts +++ b/packages/chord/test/manager.test.ts @@ -45,6 +45,24 @@ describe("processLock", () => { }); describe("host manager (real child processes)", () => { + // A child host that forks its own children used to pass its CHORD2_LOCK on, so they failed with "locked". + it("a child without a lock does not inherit the parent's lock", async () => { + const name = `chord2-test-${process.pid}-inherited`; + const release = await processLock(name); + const previous = process.env.CHORD2_LOCK; + process.env.CHORD2_LOCK = name; + const manager = createHostManager({ driver: driver() }); + try { + await manager.start(); + assert.equal(manager.status, "running"); + } finally { + if (previous === undefined) delete process.env.CHORD2_LOCK; + else process.env.CHORD2_LOCK = previous; + await manager.stop(); + await release(); + } + }); + it("starts a child host, links it, stops it gracefully", async () => { const manager = createHostManager({ driver: driver() }); const instance = await manager.start();