Protect active development Graphs from rejected launcher cleanup

This commit is contained in:
Timothy J. Aveni
2026-10-11 03:42:32 -07:00
parent 2139f138ca
commit fa698dd306
+18 -11
View File
@@ -1,14 +1,19 @@
import { spawn } from "node:child_process"; import { spawn } from "node:child_process";
export class FileLockBusyError extends Error {}
/** Kernel-owned lock: a crashed coordinator cannot leave a stale ownership file. /** 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. */ * 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> { export async function withFileLock<T>(
filename: string,
work: () => Promise<T>,
{ nonblocking = false }: { nonblocking?: boolean } = {},
): Promise<T> {
const child = spawn( const child = spawn(
"flock", "flock",
[ [
"--exclusive", "--exclusive",
"--timeout", ...(nonblocking ? ["--nonblock"] : ["--timeout", "120"]),
"120",
"--conflict-exit-code", "--conflict-exit-code",
"75", "75",
filename, filename,
@@ -32,15 +37,17 @@ export async function withFileLock<T>(filename: string, work: () => Promise<T>):
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
let output = ""; let output = "";
child.once("error", reject); child.once("error", reject);
child.once("exit", (code) => child.once("exit", (code) => {
reject( reject(
new Error( code === 75
code === 75 ? new FileLockBusyError(
? "Timed out after 120 seconds waiting for another authoring command; inspect that command before retrying" nonblocking
: `Cannot acquire authoring lock: ${diagnostics}`, ? "Another process owns this lock"
), : "Timed out after 120 seconds waiting for another authoring command; inspect that command before retrying",
), )
); : new Error(`Cannot acquire authoring lock: ${diagnostics}`),
);
});
child.stdout.on("data", (chunk) => { child.stdout.on("data", (chunk) => {
output += chunk; output += chunk;
if (output.includes("locked\n")) resolve(); if (output.includes("locked\n")) resolve();