Protect active development Graphs from rejected launcher cleanup
This commit is contained in:
@@ -1,14 +1,19 @@
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
export class FileLockBusyError extends Error {}
|
||||
|
||||
/** 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> {
|
||||
export async function withFileLock<T>(
|
||||
filename: string,
|
||||
work: () => Promise<T>,
|
||||
{ nonblocking = false }: { nonblocking?: boolean } = {},
|
||||
): Promise<T> {
|
||||
const child = spawn(
|
||||
"flock",
|
||||
[
|
||||
"--exclusive",
|
||||
"--timeout",
|
||||
"120",
|
||||
...(nonblocking ? ["--nonblock"] : ["--timeout", "120"]),
|
||||
"--conflict-exit-code",
|
||||
"75",
|
||||
filename,
|
||||
@@ -32,15 +37,17 @@ export async function withFileLock<T>(filename: string, work: () => Promise<T>):
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
let output = "";
|
||||
child.once("error", reject);
|
||||
child.once("exit", (code) =>
|
||||
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}`,
|
||||
),
|
||||
),
|
||||
);
|
||||
code === 75
|
||||
? new FileLockBusyError(
|
||||
nonblocking
|
||||
? "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) => {
|
||||
output += chunk;
|
||||
if (output.includes("locked\n")) resolve();
|
||||
|
||||
Reference in New Issue
Block a user