mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 20:35:39 -06:00
fix(e2e): keep lifecycle timeout cleanup alive (#92566)
This commit is contained in:
@@ -72,11 +72,13 @@ function readProcSnapshot() {
|
||||
.trim()
|
||||
.split(/\s+/u);
|
||||
const ppid = Number.parseInt(fields[1] ?? "", 10);
|
||||
const pgrp = Number.parseInt(fields[2] ?? "", 10);
|
||||
const userTicks = Number.parseInt(fields[11] ?? "", 10);
|
||||
const systemTicks = Number.parseInt(fields[12] ?? "", 10);
|
||||
const rssPages = Number.parseInt(fields[21] ?? "", 10);
|
||||
if (
|
||||
!Number.isFinite(ppid) ||
|
||||
!Number.isFinite(pgrp) ||
|
||||
!Number.isFinite(userTicks) ||
|
||||
!Number.isFinite(systemTicks) ||
|
||||
!Number.isFinite(rssPages)
|
||||
@@ -85,6 +87,7 @@ function readProcSnapshot() {
|
||||
}
|
||||
stats.set(pid, {
|
||||
ppid,
|
||||
pgrp,
|
||||
cpuTicks: userTicks + systemTicks,
|
||||
rssBytes: Math.max(0, rssPages) * pageSize,
|
||||
});
|
||||
@@ -117,7 +120,10 @@ function descendantsOf(rootPid, stats) {
|
||||
|
||||
function sample(rootPid) {
|
||||
const stats = readProcSnapshot();
|
||||
const pids = descendantsOf(rootPid, stats);
|
||||
const groupPids = new Set(
|
||||
[...stats.entries()].filter(([, stat]) => stat.pgrp === rootPid).map(([pid]) => pid),
|
||||
);
|
||||
const pids = new Set([...descendantsOf(rootPid, stats), ...groupPids]);
|
||||
let rssBytes = 0;
|
||||
let cpuTicks = 0;
|
||||
for (const pid of pids) {
|
||||
@@ -148,6 +154,10 @@ let forwardedParentSignal = null;
|
||||
let killTimer;
|
||||
let parentSignalTimer;
|
||||
let parentSignalPollTimer;
|
||||
let childGroupDrainTimer;
|
||||
// The leader can exit before descendants in its detached process group.
|
||||
// Keep the wrapper alive so timeout cleanup still owns those descendants.
|
||||
let childClosedResult = null;
|
||||
const updateMetrics = () => {
|
||||
if (!child.pid) {
|
||||
return;
|
||||
@@ -157,11 +167,21 @@ const updateMetrics = () => {
|
||||
maxCpuTicks = Math.max(maxCpuTicks, current.cpuTicks);
|
||||
};
|
||||
|
||||
function finishChildClosedResultIfGroupDrained() {
|
||||
if (childClosedResult && !childGroupExists()) {
|
||||
finish(childClosedResult.code, childClosedResult.signal);
|
||||
}
|
||||
}
|
||||
|
||||
updateMetrics();
|
||||
const interval = setInterval(updateMetrics, pollMs);
|
||||
const timeoutTimer =
|
||||
Number.isFinite(timeoutMs) && timeoutMs > 0
|
||||
? setTimeout(() => {
|
||||
if (childClosedResult && !childGroupExists()) {
|
||||
finish(childClosedResult.code, childClosedResult.signal);
|
||||
return;
|
||||
}
|
||||
timedOut = true;
|
||||
terminateChildGroup("SIGTERM");
|
||||
killTimer = setTimeout(() => {
|
||||
@@ -215,6 +235,9 @@ function clearRuntimeTimers() {
|
||||
if (parentSignalPollTimer) {
|
||||
clearInterval(parentSignalPollTimer);
|
||||
}
|
||||
if (childGroupDrainTimer) {
|
||||
clearInterval(childGroupDrainTimer);
|
||||
}
|
||||
}
|
||||
|
||||
function rethrowParentSignal(signal) {
|
||||
@@ -329,5 +352,10 @@ child.on("exit", (code, signal) => {
|
||||
if (timedOut && killTimer) {
|
||||
return;
|
||||
}
|
||||
if (childGroupExists()) {
|
||||
childClosedResult = { code, signal };
|
||||
childGroupDrainTimer = setInterval(finishChildClosedResultIfGroupDrained, Math.min(25, pollMs));
|
||||
return;
|
||||
}
|
||||
finish(code, signal);
|
||||
});
|
||||
|
||||
@@ -220,7 +220,11 @@ describe("plugin lifecycle resource sampler", () => {
|
||||
"--",
|
||||
"bash",
|
||||
"-lc",
|
||||
'bash -c \'trap "" TERM; printf "%s\\n" "$$" >"$PID_FILE"; while :; do sleep 1; done\' & wait',
|
||||
[
|
||||
'bash -c \'trap "" TERM; printf "%s\\n" "$$" >"$PID_FILE"; while :; do sleep 1; done\' &',
|
||||
'while [ ! -s "$PID_FILE" ]; do sleep 0.01; done',
|
||||
"exit 0",
|
||||
].join("\n"),
|
||||
],
|
||||
{
|
||||
cwd: process.cwd(),
|
||||
|
||||
Reference in New Issue
Block a user