diff --git a/src/agents/session-write-lock.ts b/src/agents/session-write-lock.ts index 6c6bf89745..94d43d5ac8 100644 --- a/src/agents/session-write-lock.ts +++ b/src/agents/session-write-lock.ts @@ -1,6 +1,7 @@ import fsSync from "node:fs"; import fs from "node:fs/promises"; import path from "node:path"; +import { isPidAlive } from "../shared/pid-alive.js"; type LockFilePayload = { pid: number; @@ -48,18 +49,6 @@ function resolveCleanupState(): CleanupState { return proc[CLEANUP_STATE_KEY]; } -function isAlive(pid: number): boolean { - if (!Number.isFinite(pid) || pid <= 0) { - return false; - } - try { - process.kill(pid, 0); - return true; - } catch { - return false; - } -} - /** * Synchronously release all held locks. * Used during process exit when async operations aren't reliable. @@ -206,7 +195,7 @@ export async function acquireSessionWriteLock(params: { const payload = await readLockPayload(lockPath); const createdAt = payload?.createdAt ? Date.parse(payload.createdAt) : NaN; const stale = !Number.isFinite(createdAt) || Date.now() - createdAt > staleMs; - const alive = payload?.pid ? isAlive(payload.pid) : false; + const alive = payload?.pid ? isPidAlive(payload.pid) : false; if (stale || !alive) { await fs.rm(lockPath, { force: true }); continue; diff --git a/src/infra/gateway-lock.ts b/src/infra/gateway-lock.ts index ef89f42a10..d6dbf2266a 100644 --- a/src/infra/gateway-lock.ts +++ b/src/infra/gateway-lock.ts @@ -3,6 +3,7 @@ import fsSync from "node:fs"; import fs from "node:fs/promises"; import path from "node:path"; import { resolveConfigPath, resolveGatewayLockDir, resolveStateDir } from "../config/paths.js"; +import { isPidAlive } from "../shared/pid-alive.js"; const DEFAULT_TIMEOUT_MS = 5000; const DEFAULT_POLL_INTERVAL_MS = 100; @@ -42,18 +43,6 @@ export class GatewayLockError extends Error { type LockOwnerStatus = "alive" | "dead" | "unknown"; -function isAlive(pid: number): boolean { - if (!Number.isFinite(pid) || pid <= 0) { - return false; - } - try { - process.kill(pid, 0); - return true; - } catch { - return false; - } -} - function normalizeProcArg(arg: string): string { return arg.replaceAll("\\", "/").toLowerCase(); } @@ -116,7 +105,7 @@ function resolveGatewayOwnerStatus( payload: LockPayload | null, platform: NodeJS.Platform, ): LockOwnerStatus { - if (!isAlive(pid)) { + if (!isPidAlive(pid)) { return "dead"; } if (platform !== "linux") { diff --git a/src/plugin-sdk/file-lock.ts b/src/plugin-sdk/file-lock.ts index 2e38607da6..9bbf2e5b6c 100644 --- a/src/plugin-sdk/file-lock.ts +++ b/src/plugin-sdk/file-lock.ts @@ -1,5 +1,6 @@ import fs from "node:fs/promises"; import path from "node:path"; +import { isPidAlive } from "../shared/pid-alive.js"; export type FileLockOptions = { retries: { @@ -37,18 +38,6 @@ function resolveHeldLocks(): Map { const HELD_LOCKS = resolveHeldLocks(); -function isAlive(pid: number): boolean { - if (!Number.isFinite(pid) || pid <= 0) { - return false; - } - try { - process.kill(pid, 0); - return true; - } catch { - return false; - } -} - function computeDelayMs(retries: FileLockOptions["retries"], attempt: number): number { const base = Math.min( retries.maxTimeout, @@ -85,7 +74,7 @@ async function resolveNormalizedFilePath(filePath: string): Promise { async function isStaleLock(lockPath: string, staleMs: number): Promise { const payload = await readLockPayload(lockPath); - if (payload?.pid && !isAlive(payload.pid)) { + if (payload?.pid && !isPidAlive(payload.pid)) { return true; } if (payload?.createdAt) { diff --git a/src/shared/pid-alive.ts b/src/shared/pid-alive.ts new file mode 100644 index 0000000000..a1e9c84eac --- /dev/null +++ b/src/shared/pid-alive.ts @@ -0,0 +1,11 @@ +export function isPidAlive(pid: number): boolean { + if (!Number.isFinite(pid) || pid <= 0) { + return false; + } + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } +}