























@@ -11,6 +11,14 @@ if (!summaryPath || !phase || separator !== "--" || !command) {
1111const pageSize = Number.parseInt(process.env.OPENCLAW_PROC_PAGE_SIZE || "4096", 10);
1212const clockTicks = Number.parseInt(process.env.OPENCLAW_PROC_CLK_TCK || "100", 10);
1313const pollMs = Number.parseInt(process.env.OPENCLAW_PLUGIN_LIFECYCLE_METRIC_POLL_MS || "100", 10);
14+const timeoutMs = Number.parseInt(
15+process.env.OPENCLAW_PLUGIN_LIFECYCLE_PHASE_TIMEOUT_MS || "300000",
16+10,
17+);
18+const timeoutKillGraceMs = Number.parseInt(
19+process.env.OPENCLAW_PLUGIN_LIFECYCLE_TIMEOUT_KILL_GRACE_MS || "2000",
20+10,
21+);
14221523if (!fs.existsSync("/proc")) {
1624console.error("plugin lifecycle resource sampler requires Linux /proc");
@@ -99,11 +107,16 @@ const started = performance.now();
99107const child = spawn(command, args, {
100108cwd: process.cwd(),
101109env: process.env,
110+detached: true,
102111stdio: "inherit",
103112});
104113105114let maxRssBytes = 0;
106115let maxCpuTicks = 0;
116+let timedOut = false;
117+let finished = false;
118+let parentSignalInFlight = false;
119+let killTimer;
107120const updateMetrics = () => {
108121if (!child.pid) {
109122return;
@@ -115,24 +128,119 @@ const updateMetrics = () => {
115128116129updateMetrics();
117130const interval = setInterval(updateMetrics, pollMs);
131+const timeoutTimer =
132+Number.isFinite(timeoutMs) && timeoutMs > 0
133+ ? setTimeout(() => {
134+timedOut = true;
135+terminateChildGroup("SIGTERM");
136+killTimer = setTimeout(() => {
137+terminateChildGroup("SIGKILL");
138+finish(124);
139+}, timeoutKillGraceMs);
140+killTimer.unref?.();
141+}, timeoutMs)
142+ : null;
143+timeoutTimer?.unref?.();
118144119-child.on("exit", (code, signal) => {
120-updateMetrics();
145+function terminateChildGroup(signal) {
146+if (!child.pid) {
147+return;
148+}
149+try {
150+process.kill(-child.pid, signal);
151+return;
152+} catch {}
153+try {
154+child.kill(signal);
155+} catch {}
156+}
157+158+function clearRuntimeTimers() {
121159clearInterval(interval);
160+if (timeoutTimer) {
161+clearTimeout(timeoutTimer);
162+}
163+if (killTimer) {
164+clearTimeout(killTimer);
165+}
166+}
167+168+function rethrowParentSignal(signal) {
169+process.removeAllListeners(signal);
170+process.kill(process.pid, signal);
171+process.exit(128);
172+}
173+174+function handleParentSignal(signal) {
175+if (parentSignalInFlight) {
176+terminateChildGroup("SIGKILL");
177+rethrowParentSignal(signal);
178+return;
179+}
180+parentSignalInFlight = true;
181+if (finished) {
182+rethrowParentSignal(signal);
183+return;
184+}
185+finished = true;
186+clearRuntimeTimers();
187+terminateChildGroup(signal);
188+setTimeout(() => {
189+terminateChildGroup("SIGKILL");
190+rethrowParentSignal(signal);
191+}, timeoutKillGraceMs);
192+}
193+194+for (const signal of ["SIGHUP", "SIGINT", "SIGTERM"]) {
195+process.once(signal, () => handleParentSignal(signal));
196+}
197+198+process.once("exit", () => {
199+if (!finished) {
200+terminateChildGroup("SIGTERM");
201+}
202+});
203+204+function finish(code, signal) {
205+if (finished) {
206+return;
207+}
208+finished = true;
209+updateMetrics();
210+clearRuntimeTimers();
122211const wallMs = performance.now() - started;
123212const cpuSeconds = maxCpuTicks / clockTicks;
124213const maxRssKb = Math.round(maxRssBytes / 1024);
125214const cpuCoreRatio = wallMs > 0 ? cpuSeconds / (wallMs / 1000) : 0;
215+const summarySignal = timedOut ? "timeout" : (signal ?? "");
126216fs.appendFileSync(
127217summaryPath,
128-`${phase}\t${maxRssKb}\t${cpuSeconds.toFixed(3)}\t${wallMs.toFixed(0)}\t${cpuCoreRatio.toFixed(3)}\t${signal ?? ""}\n`,
218+`${phase}\t${maxRssKb}\t${cpuSeconds.toFixed(3)}\t${wallMs.toFixed(0)}\t${cpuCoreRatio.toFixed(3)}\t${summarySignal}\n`,
129219);
130220console.log(
131-`plugin lifecycle resource: phase=${phase} max_rss_kb=${maxRssKb} cpu_s=${cpuSeconds.toFixed(3)} wall_ms=${wallMs.toFixed(0)} cpu_core_ratio=${cpuCoreRatio.toFixed(3)}`,
221+`plugin lifecycle resource: phase=${phase} max_rss_kb=${maxRssKb} cpu_s=${cpuSeconds.toFixed(3)} wall_ms=${wallMs.toFixed(0)} cpu_core_ratio=${cpuCoreRatio.toFixed(3)} signal=${summarySignal}`,
132222);
223+if (timedOut) {
224+process.exit(124);
225+return;
226+}
133227if (signal) {
134228process.kill(process.pid, signal);
135229return;
136230}
137231process.exit(code ?? 0);
232+}
233+234+child.on("error", (error) => {
235+finished = true;
236+clearRuntimeTimers();
237+console.error(error instanceof Error ? error.message : String(error));
238+process.exit(1);
239+});
240+241+child.on("exit", (code, signal) => {
242+if (timedOut && killTimer) {
243+return;
244+}
245+finish(code, signal);
138246});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。