import { mkdir, open, rename, rm, stat } from 'node:fs/promises'; import { dirname } from 'node:path'; const STALE_LOCK_MS = 30_000; function errnoCode(error: unknown): string | undefined { return error instanceof Error && 'code' in error ? (error as NodeJS.ErrnoException).code : undefined; } export async function withCinemaFileLock(path: string, operation: () => Promise): Promise { const lockPath = `${path}.lock`; const deadline = Date.now() + 10_000; await mkdir(dirname(path), { recursive: true }); let release: (() => Promise) | null = null; while (!release) { try { const handle = await open(lockPath, 'wx'); await handle.writeFile(JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() })); release = async () => { await handle.close(); await rm(lockPath, { force: true }); }; } catch (error) { if (errnoCode(error) !== 'EEXIST') throw error; try { if (Date.now() - (await stat(lockPath)).mtimeMs > STALE_LOCK_MS) { const claimed = `${lockPath}.stale-${process.pid}-${Date.now()}`; try { await rename(lockPath, claimed); await rm(claimed, { force: true }); } catch (claimError) { if (errnoCode(claimError) !== 'ENOENT') throw claimError; } } } catch (statError) { if (errnoCode(statError) !== 'ENOENT') throw statError; } if (Date.now() >= deadline) throw new Error(`timed out waiting for Cinema file lock: ${lockPath}`); await new Promise((resolve) => setTimeout(resolve, 20)); } } try { return await operation(); } finally { await release(); } }