From 0cc84eca3f1575d1c5213425be8092a77d01ba3d Mon Sep 17 00:00:00 2001 From: mldangelo-oai <269034524+mldangelo-oai@users.noreply.github.com> Date: Tue, 18 Aug 2026 10:26:36 -0700 Subject: [PATCH] fix: preserve exact multiscan lock identities --- sdk/typescript/src/multiscan.ts | 4 ++-- sdk/typescript/tests-ts/multiscan.test.ts | 28 ++++++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/sdk/typescript/src/multiscan.ts b/sdk/typescript/src/multiscan.ts index 63f3a7909..d5d8f6e51 100644 --- a/sdk/typescript/src/multiscan.ts +++ b/sdk/typescript/src/multiscan.ts @@ -419,7 +419,7 @@ async function acquireLock(output: string): Promise<() => Promise> { await rm(stale, { recursive: true, force: true }); } } - const createdLock = await lstat(path); + const createdLock = await lstat(path, { bigint: true }); const owner = `${JSON.stringify({ pid: process.pid, ownerId: randomUUID(), @@ -429,7 +429,7 @@ async function acquireLock(output: string): Promise<() => Promise> { try { await writeFile(ownerPath, owner, { flag: "wx", mode: 0o600 }); } catch (error) { - const currentLock = await lstat(path).catch( + const currentLock = await lstat(path, { bigint: true }).catch( (cleanup: NodeJS.ErrnoException) => { if (cleanup.code !== "ENOENT") throw cleanup; return undefined; diff --git a/sdk/typescript/tests-ts/multiscan.test.ts b/sdk/typescript/tests-ts/multiscan.test.ts index 2c62c0422..9f229fe04 100644 --- a/sdk/typescript/tests-ts/multiscan.test.ts +++ b/sdk/typescript/tests-ts/multiscan.test.ts @@ -1388,8 +1388,16 @@ describe("multiscan", () => { }); test.each([false, true])( - "never removes a replacement lock when owner creation fails (owner published: %s)", + "never removes a replacement lock when owner creation fails (owner published: %p)", async (ownerPublished) => { + if ( + runTestInSubprocess( + import.meta.path, + `never removes a replacement lock when owner creation fails (owner published: ${ownerPublished})`, + ) + ) { + return; + } const paths = await fixture(); const source = await repository(paths.root, "owner-creation-race"); await writeFile( @@ -1404,7 +1412,23 @@ describe("multiscan", () => { hostname: hostname(), processStartedAt: performance.timeOrigin, }); + const createdInode = 2n ** 60n; + const replacementInode = createdInode + 1n; + expect(Number(createdInode)).toBe(Number(replacementInode)); + let replaced = false; + const originalLstat = filesystem.lstat; const originalWriteFile = filesystem.writeFile; + const readLock = spyOn(filesystem, "lstat").mockImplementation((async ( + ...args: Parameters + ) => { + const metadata = await originalLstat(...args); + if (String(args[0]) === lock) { + const inode = replaced ? replacementInode : createdInode; + metadata.ino = + typeof metadata.ino === "bigint" ? inode : Number(inode); + } + return metadata; + }) as typeof filesystem.lstat); const writeOwner = spyOn(filesystem, "writeFile").mockImplementation( async (path, data, options) => { if (String(path) !== ownerPath) { @@ -1413,6 +1437,7 @@ describe("multiscan", () => { writeOwner.mockRestore(); await rename(lock, join(paths.output, ".lock.stale-owner-creation")); await mkdir(lock, { mode: 0o700 }); + replaced = true; if (ownerPublished) { await originalWriteFile(ownerPath, replacement, { mode: 0o600 }); } @@ -1439,6 +1464,7 @@ describe("multiscan", () => { } } finally { writeOwner.mockRestore(); + readLock.mockRestore(); } }, );