2026-09-14 10:26:11 -07:00
import { spawn } from "node:child_process" ;
/** Kernel-owned lock: a crashed coordinator cannot leave a stale ownership file.
* The persistent file is just an inode; EOF releases the helper's lock. */
export async function withFileLock < T >( filename : string , work : () => Promise < T >) : Promise < T > {
2026-09-14 15:24:37 -07:00
const child = spawn ( "flock" , [ "--exclusive" , "--timeout" , "120" , "--conflict-exit-code" , "75" , filename ,
2026-09-14 10:26:11 -07:00
process . execPath , "-e" , 'process.stdout.write("locked\\n"); process.stdin.resume();' ], { stdio : [ "pipe" , "pipe" , "pipe" ]});
let diagnostics = "" ;
child . stdin . on ( "error" , () => { /* acquisition/exit handling reports helper failure */ });
child . stderr . on ( "data" , chunk => { diagnostics = ( diagnostics + String ( chunk )). slice ( - 2000 ); });
const closed = new Promise < void >(( resolve ) => { child . once ( "close" , () => resolve ()); });
try {
await new Promise < void >(( resolve , reject ) => {
let output = "" ;
child . once ( "error" , reject );
2026-09-14 15:24:37 -07:00
child . once ( "exit" , code => reject ( new Error ( code === 75 ? "Timed out after 120 seconds waiting for another authoring command; inspect that command before retrying" : `Cannot acquire authoring lock: ${ diagnostics } ` )));
2026-09-14 10:26:11 -07:00
child . stdout . on ( "data" , chunk => { output += chunk ; if ( output . includes ( "locked\n" )) resolve (); });
});
return await work ();
} finally { child . stdin . end (); await closed ; }
}