mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-10 00:11:19 +00:00
fix(scripts): clamp package docker timers
This commit is contained in:
parent
75c6a8fff5
commit
851b65c060
2 changed files with 78 additions and 5 deletions
|
|
@ -17,6 +17,7 @@ const DEFAULT_TIMEOUT_KILL_AFTER_MS = 5_000;
|
|||
const PROCESS_GROUP_EXIT_POLL_MS = 25;
|
||||
const POST_FORCE_KILL_WAIT_MS = 1_000;
|
||||
const DEFAULT_CAPTURED_STDOUT_MAX_BYTES = 1024 * 1024;
|
||||
const MAX_TIMER_TIMEOUT_MS = 2_147_000_000;
|
||||
const ACTIVE_CHILD_KILLERS = new Set();
|
||||
const SIGNAL_EXIT_CODES = {
|
||||
SIGHUP: 129,
|
||||
|
|
@ -65,6 +66,23 @@ function resolveTimeoutMs(envName, defaultValue) {
|
|||
return parsed;
|
||||
}
|
||||
|
||||
function numericTimerValueMs(valueMs) {
|
||||
const value = Number(valueMs);
|
||||
return Number.isFinite(value) ? Math.floor(value) : undefined;
|
||||
}
|
||||
|
||||
function resolveTimerTimeoutMs(valueMs, fallbackMs = MAX_TIMER_TIMEOUT_MS) {
|
||||
const value = numericTimerValueMs(valueMs) ?? numericTimerValueMs(fallbackMs);
|
||||
return Math.min(Math.max(value ?? MAX_TIMER_TIMEOUT_MS, 1), MAX_TIMER_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
function resolveOptionalTimerTimeoutMs(valueMs) {
|
||||
if (valueMs === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return resolveTimerTimeoutMs(valueMs, 1);
|
||||
}
|
||||
|
||||
function readOptionValue(argv, index, optionName) {
|
||||
const value = argv[index + 1];
|
||||
if (value === undefined || value === "" || value.startsWith("-")) {
|
||||
|
|
@ -149,6 +167,11 @@ export function parseArgs(argv) {
|
|||
|
||||
function run(command, args, cwd, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const resolvedTimeoutMs = resolveOptionalTimerTimeoutMs(options.timeoutMs);
|
||||
const resolvedKillAfterMs = resolveTimerTimeoutMs(
|
||||
options.killAfterMs,
|
||||
DEFAULT_TIMEOUT_KILL_AFTER_MS,
|
||||
);
|
||||
const useProcessGroup = process.platform !== "win32";
|
||||
const child = spawn(command, args, {
|
||||
cwd,
|
||||
|
|
@ -230,21 +253,21 @@ function run(command, args, cwd, options = {}) {
|
|||
return;
|
||||
}
|
||||
killChild("SIGKILL");
|
||||
}, options.killAfterMs ?? DEFAULT_TIMEOUT_KILL_AFTER_MS);
|
||||
}, resolvedKillAfterMs);
|
||||
forceKillTimeout.unref?.();
|
||||
};
|
||||
ACTIVE_CHILD_KILLERS.add(killChild);
|
||||
const timeout =
|
||||
options.timeoutMs === undefined
|
||||
resolvedTimeoutMs === undefined
|
||||
? undefined
|
||||
: setTimeout(() => {
|
||||
timedOut = true;
|
||||
terminateChild();
|
||||
}, options.timeoutMs);
|
||||
}, resolvedTimeoutMs);
|
||||
timeout?.unref?.();
|
||||
const finishAfterTeardown = async (error, value = "") => {
|
||||
if (processGroupAlive()) {
|
||||
await waitForProcessGroupExit(options.killAfterMs ?? DEFAULT_TIMEOUT_KILL_AFTER_MS);
|
||||
await waitForProcessGroupExit(resolvedKillAfterMs);
|
||||
}
|
||||
if (processGroupAlive()) {
|
||||
killChild("SIGKILL");
|
||||
|
|
@ -275,7 +298,7 @@ function run(command, args, cwd, options = {}) {
|
|||
child.on("close", (status, signal) => {
|
||||
if (timedOut) {
|
||||
void finishAfterTeardown(
|
||||
new Error(`${command} ${args.join(" ")} timed out after ${options.timeoutMs}ms`),
|
||||
new Error(`${command} ${args.join(" ")} timed out after ${resolvedTimeoutMs}ms`),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import fs from "node:fs";
|
|||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
buildPackageArtifacts,
|
||||
|
|
@ -316,6 +317,20 @@ describe("package-openclaw-for-docker", () => {
|
|||
expect(calls).toEqual(["prepare:/repo", "pack", "restore:/repo"]);
|
||||
});
|
||||
|
||||
it("clamps oversized command timers before scheduling", async () => {
|
||||
await expect(
|
||||
runCommandForTest(
|
||||
process.execPath,
|
||||
["-e", "setTimeout(() => process.exit(0), 25);"],
|
||||
process.cwd(),
|
||||
{
|
||||
killAfterMs: MAX_TIMER_TIMEOUT_MS + 1,
|
||||
timeoutMs: MAX_TIMER_TIMEOUT_MS + 1,
|
||||
},
|
||||
),
|
||||
).resolves.toBe("");
|
||||
});
|
||||
|
||||
it("kills timed-out child process groups", async () => {
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
|
|
@ -354,6 +369,41 @@ describe("package-openclaw-for-docker", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("clamps oversized kill grace before scheduling", async () => {
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
}
|
||||
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-package-grace-"));
|
||||
const donePath = path.join(tempDir, "done");
|
||||
const childPidPath = path.join(tempDir, "child.pid");
|
||||
let childPid;
|
||||
try {
|
||||
const script = [
|
||||
"const fs = require('node:fs');",
|
||||
`fs.writeFileSync(${JSON.stringify(childPidPath)}, String(process.pid));`,
|
||||
"process.on('SIGTERM', () => {",
|
||||
` setTimeout(() => { fs.writeFileSync(${JSON.stringify(donePath)}, 'done'); process.exit(0); }, 75);`,
|
||||
"});",
|
||||
"setInterval(() => {}, 1000);",
|
||||
].join("\n");
|
||||
|
||||
const runPromise = runCommandForTest(process.execPath, ["-e", script], process.cwd(), {
|
||||
killAfterMs: MAX_TIMER_TIMEOUT_MS + 1,
|
||||
timeoutMs: 500,
|
||||
});
|
||||
childPid = await readPid(childPidPath, 2000);
|
||||
|
||||
await expect(runPromise).rejects.toThrow(/timed out after 500ms/u);
|
||||
expect(fs.readFileSync(donePath, "utf8")).toBe("done");
|
||||
} finally {
|
||||
if (childPid && isProcessAlive(childPid)) {
|
||||
process.kill(childPid, "SIGKILL");
|
||||
}
|
||||
fs.rmSync(tempDir, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps fallback SIGKILL armed for descendants after the direct child exits", async () => {
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue