mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 17:53:39 +00:00
* refactor(scripts): deslop scripts/lib Consolidate script lifecycle, scenario, planner, report, release and packaging helpers while retaining CLI and publication contracts. Keep config-boundary source caches within one scan so repeated in-process checks observe edits. Carry extracted helpers through selective fixtures and iOS scope routing. Leave PR tooling and its library import closure intact. * fix(scripts): retain sanitizer fast path after helper cleanup * fix(scripts): model watchdog shell consumer in dead-code checks
117 lines
3.4 KiB
JavaScript
117 lines
3.4 KiB
JavaScript
// Shared Node fallback for host Docker and in-container E2E command timeouts.
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
export async function runWatchdog(kind, timeoutValue, [command, ...args]) {
|
|
const docker = kind === "docker";
|
|
const label = docker ? "Docker" : "OpenClaw E2E";
|
|
|
|
const parseTimeoutMs = (value) => {
|
|
const match = /^([0-9]+(?:\.[0-9]+)?)(ms|s|m|h)?$/u.exec(String(value ?? "").trim());
|
|
if (!match) {
|
|
throw new Error(`unsupported timeout value: ${value}`);
|
|
}
|
|
const amount = Number(match[1]);
|
|
const unit = match[2] ?? "s";
|
|
const multiplier = unit === "ms" ? 1 : unit === "s" ? 1_000 : unit === "m" ? 60_000 : 3_600_000;
|
|
return Math.max(1, Math.ceil(amount * multiplier));
|
|
};
|
|
|
|
if (!command) {
|
|
console.error("missing command for Node watchdog");
|
|
process.exit(1);
|
|
}
|
|
|
|
const { spawn } = await import("node:child_process");
|
|
let timeoutMs;
|
|
try {
|
|
timeoutMs = parseTimeoutMs(timeoutValue);
|
|
} catch (error) {
|
|
console.error(error instanceof Error ? error.message : String(error));
|
|
process.exit(1);
|
|
}
|
|
|
|
const child = spawn(command, args, {
|
|
detached: process.platform !== "win32",
|
|
stdio: "inherit",
|
|
});
|
|
let timedOut = false;
|
|
let parentSignal = null;
|
|
let parentSignalTimer = null;
|
|
const signalExitCodes = new Map([
|
|
["SIGHUP", 129],
|
|
["SIGINT", 130],
|
|
["SIGTERM", 143],
|
|
]);
|
|
const killGraceMs = Number.parseInt(
|
|
process.env[
|
|
docker ? "OPENCLAW_DOCKER_TIMEOUT_KILL_GRACE_MS" : "OPENCLAW_E2E_TIMEOUT_KILL_GRACE_MS"
|
|
] || "30000",
|
|
10,
|
|
);
|
|
const killChild = (signal) => {
|
|
if (!child.pid) {
|
|
return;
|
|
}
|
|
const killTarget = process.platform === "win32" ? child.pid : -child.pid;
|
|
try {
|
|
process.kill(killTarget, signal);
|
|
} catch {
|
|
try {
|
|
child.kill(signal);
|
|
} catch {}
|
|
}
|
|
};
|
|
const timer = setTimeout(() => {
|
|
timedOut = true;
|
|
console.error(`${label} command timed out after ${timeoutValue}`);
|
|
killChild("SIGTERM");
|
|
setTimeout(() => killChild("SIGKILL"), killGraceMs).unref();
|
|
}, timeoutMs);
|
|
const forwardSignal = (signal) => {
|
|
if (parentSignal) {
|
|
killChild("SIGKILL");
|
|
process.exit(signalExitCodes.get(signal) ?? 1);
|
|
}
|
|
parentSignal = signal;
|
|
clearTimeout(timer);
|
|
killChild(signal);
|
|
parentSignalTimer = setTimeout(() => {
|
|
killChild("SIGKILL");
|
|
process.exit(signalExitCodes.get(signal) ?? 1);
|
|
}, killGraceMs);
|
|
parentSignalTimer.unref();
|
|
};
|
|
process.once("SIGINT", forwardSignal);
|
|
process.once("SIGTERM", forwardSignal);
|
|
process.once("SIGHUP", forwardSignal);
|
|
// In-container commands settle their inherited streams; host Docker wrappers settle on exit.
|
|
child.on(docker ? "exit" : "close", (code, signal) => {
|
|
clearTimeout(timer);
|
|
if (parentSignalTimer) {
|
|
clearTimeout(parentSignalTimer);
|
|
}
|
|
if (timedOut) {
|
|
process.exit(124);
|
|
}
|
|
if (parentSignal) {
|
|
process.exit(signalExitCodes.get(parentSignal) ?? 1);
|
|
}
|
|
if (code !== null) {
|
|
process.exit(code);
|
|
}
|
|
if (signal) {
|
|
process.kill(process.pid, signal);
|
|
}
|
|
process.exit(1);
|
|
});
|
|
child.on("error", (error) => {
|
|
clearTimeout(timer);
|
|
console.error(error.message);
|
|
process.exit(127);
|
|
});
|
|
}
|
|
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
const [kind, timeoutValue, ...args] = process.argv.slice(2);
|
|
await runWatchdog(kind, timeoutValue, args);
|
|
}
|