From 594bad7ec4313629c8dd88eac53978e61fa99d89 Mon Sep 17 00:00:00 2001 From: Colby Mchenry Date: Sun, 27 Sep 2026 15:01:02 +0000 Subject: [PATCH] test(writer-lock): hold the lock with a live child, not a pid assumed alive (#2045) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The live-holder case wrote `pid: 1` and leaned on its own comment, "On Linux, PID 1 is almost always alive". Windows has no PID 1, so tryAcquireWriterLock read the holder as dead and acquired — the correct behaviour for a dead holder, and a failing assertion. The platform-specific part was the fixture, not the lock. A parked child process is alive on every platform, and not stealing from a live holder is what the lock actually promises, so the case now tests the promise rather than a number that happens to name a running process. The child is reaped in afterEach. Fixes #1752. (cherry picked from commit bee140288dda73cfa9a9fb2f76c22d3969e79fc6) Co-authored-by: Aaron Queen --- __tests__/writer-lock.test.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/__tests__/writer-lock.test.ts b/__tests__/writer-lock.test.ts index f3c18a647..a13b11350 100644 --- a/__tests__/writer-lock.test.ts +++ b/__tests__/writer-lock.test.ts @@ -60,18 +60,26 @@ describe('writer lock (#1740)', () => { it('reports taken when a live foreign pid holds the lock', () => { const root = makeProject(); - // Use our own pid first, then overwrite with a fake live-looking pid by - // writing a pid that is alive: process.pid of this test — simulate foreign - // by writing a different alive pid. On Linux, PID 1 is almost always alive. + // A foreign process that is genuinely alive, rather than a pid assumed to + // be: PID 1 is init on Linux but does not exist on Windows, where the lock + // then reads the holder as dead and correctly acquires — the assertion was + // failing on the fixture, not on the lock. A parked child is alive + // everywhere, and it is what the lock actually promises not to steal from. + // Uses the shared holder so afterEach reaps it (same as the fallback- + // engine case below). + holder = spawn(process.execPath, ['-e', 'setTimeout(() => {}, 60000)'], { + stdio: 'ignore', + }); + if (!holder.pid) throw new Error('Failed to spawn writer-lock holder'); fs.writeFileSync( getWriterPidPath(root), - JSON.stringify({ pid: 1, mode: 'direct', startedAt: Date.now() }) + '\n', + JSON.stringify({ pid: holder.pid, mode: 'direct', startedAt: Date.now() }) + '\n', { flag: 'wx' }, ); const r = tryAcquireWriterLock(root, 'direct'); expect(r.kind).toBe('taken'); if (r.kind === 'taken') { - expect(r.existing?.pid).toBe(1); + expect(r.existing?.pid).toBe(holder.pid); const msg = writerLockHeldMessage(r.existing, r.pidPath); expect(msg).toMatch(/writer lock held/i); expect(msg).toMatch(/CODEGRAPH_NO_DAEMON/);