Files
openclaw/src/shared/pid-alive.ts
Peter Steinberger 1e7510ae10 docs: continue inline comment pass (#88849)
Adds broad inline comments and JSDoc for CLI, cron, outbound/channel, plugin SDK, ACP, shared helpers, net policy, and related utility contracts. Proof: git diff --check on latest exact head plus focused cron tests passed; CI had no failing checks observed before merge attempt.
2026-05-31 22:32:28 -04:00

85 lines
2.4 KiB
TypeScript

import fsSync from "node:fs";
function isValidPid(pid: number): boolean {
return Number.isInteger(pid) && pid > 0;
}
/**
* Check if a process is a zombie on Linux by reading /proc/<pid>/status.
* Returns false on non-Linux platforms or if the proc file can't be read.
*/
function isZombieProcess(pid: number): boolean {
if (process.platform !== "linux") {
return false;
}
try {
const status = fsSync.readFileSync(`/proc/${pid}/status`, "utf8");
const stateMatch = status.match(/^State:\s+(\S)/m);
return stateMatch?.[1] === "Z";
} catch {
return false;
}
}
/** Returns true only when a positive PID exists and is not a Linux zombie process. */
export function isPidAlive(pid: number): boolean {
if (!isValidPid(pid)) {
return false;
}
try {
process.kill(pid, 0);
} catch {
return false;
}
if (isZombieProcess(pid)) {
return false;
}
return true;
}
/** Returns true only when the PID is invalid, missing, or known to be a Linux zombie. */
export function isPidDefinitelyDead(pid: number): boolean {
if (!isValidPid(pid)) {
return true;
}
try {
process.kill(pid, 0);
} catch (err) {
return (err as NodeJS.ErrnoException).code === "ESRCH";
}
return isZombieProcess(pid);
}
/**
* Read the process start time (field 22 "starttime") from /proc/<pid>/stat.
* Returns the value in clock ticks since system boot, or null on non-Linux
* platforms or if the proc file can't be read.
*
* This is used to detect PID recycling: if two readings for the same PID
* return different starttimes, the PID has been reused by a different process.
*/
export function getProcessStartTime(pid: number): number | null {
if (process.platform !== "linux") {
return null;
}
if (!isValidPid(pid)) {
return null;
}
try {
const stat = fsSync.readFileSync(`/proc/${pid}/stat`, "utf8");
const commEndIndex = stat.lastIndexOf(")");
if (commEndIndex < 0) {
return null;
}
// The comm field (field 2) is wrapped in parens and can contain spaces,
// so split after the last ")" to get fields 3..N reliably.
const afterComm = stat.slice(commEndIndex + 1).trimStart();
const fields = afterComm.split(/\s+/);
// field 22 (starttime) = index 19 after the comm-split (field 3 is index 0).
const starttime = Number(fields[19]);
return Number.isInteger(starttime) && starttime >= 0 ? starttime : null;
} catch {
return null;
}
}