

























11// Measures gateway RPC round-trip time by launching an isolated local gateway
22// and writing qa-lab-compatible summary artifacts.
3-import { spawn } from "node:child_process";
3+import { spawn, spawnSync } from "node:child_process";
44import { randomUUID } from "node:crypto";
55import { existsSync } from "node:fs";
66import fs from "node:fs/promises";
@@ -264,6 +264,10 @@ function defaultKillProcess(pid, signal) {
264264return process.kill(pid, signal);
265265}
266266267+function defaultRunTaskkill(command, args, options) {
268+return spawnSync(command, args, options);
269+}
270+267271async function defaultOpen(filePath, flags) {
268272return await fs.open(filePath, flags);
269273}
@@ -277,10 +281,15 @@ function resolveOpenClawLaunchArgs(repoRoot, sourceEntryExists = existsSync) {
277281}
278282279283/**
280- * Signals the gateway process group on POSIX so spawned children are cleaned up.
284+ * Signals the gateway process tree so spawned package-manager children are cleaned up.
281285 */
282-export function signalGatewayProcess(child, signal, killProcess = defaultKillProcess) {
283-if (process.platform !== "win32" && typeof child.pid === "number") {
286+export function signalGatewayProcess(
287+child,
288+signal,
289+killProcess = defaultKillProcess,
290+{ platform = process.platform, runTaskkill = defaultRunTaskkill } = {},
291+) {
292+if (platform !== "win32" && typeof child.pid === "number") {
284293try {
285294killProcess(-child.pid, signal);
286295return true;
@@ -291,6 +300,16 @@ export function signalGatewayProcess(child, signal, killProcess = defaultKillPro
291300throw error;
292301}
293302}
303+if (platform === "win32" && typeof child.pid === "number") {
304+const args = ["/PID", String(child.pid), "/T"];
305+if (signal === "SIGKILL") {
306+args.push("/F");
307+}
308+const result = runTaskkill("taskkill", args, { stdio: "ignore" });
309+if (!result?.error && result?.status === 0) {
310+return true;
311+}
312+}
294313try {
295314return child.kill(signal);
296315} catch (error) {
@@ -304,8 +323,12 @@ export function signalGatewayProcess(child, signal, killProcess = defaultKillPro
304323/**
305324 * Checks process-group liveness without treating an already-exited child as an error.
306325 */
307-export function isGatewayProcessAlive(child, killProcess = defaultKillProcess) {
308-if (process.platform !== "win32" && typeof child.pid === "number") {
326+export function isGatewayProcessAlive(
327+child,
328+killProcess = defaultKillProcess,
329+{ platform = process.platform } = {},
330+) {
331+if (platform !== "win32" && typeof child.pid === "number") {
309332try {
310333killProcess(-child.pid, 0);
311334return true;
@@ -319,17 +342,17 @@ export function isGatewayProcessAlive(child, killProcess = defaultKillProcess) {
319342return child.exitCode === null && child.signalCode === null;
320343}
321344322-function signalGatewayProcessForParentExit(child, signal, killProcess) {
345+function signalGatewayProcessForParentExit(child, signal, killProcess, signalOptions) {
323346try {
324-signalGatewayProcess(child, signal, killProcess);
347+signalGatewayProcess(child, signal, killProcess, signalOptions);
325348} catch {
326349// Parent shutdown cleanup is best effort; the original signal should win.
327350}
328351}
329352330-function gatewayProcessAliveForParentExit(child, killProcess) {
353+function gatewayProcessAliveForParentExit(child, killProcess, signalOptions) {
331354try {
332-return isGatewayProcessAlive(child, killProcess);
355+return isGatewayProcessAlive(child, killProcess, signalOptions);
333356} catch {
334357return true;
335358}
@@ -340,28 +363,37 @@ function gatewayProcessAliveForParentExit(child, killProcess) {
340363 */
341364export function installGatewayParentCleanup(
342365child,
343-{ killProcess = defaultKillProcess, processLike = process } = {},
366+{
367+ killProcess = defaultKillProcess,
368+ platform = process.platform,
369+ processLike = process,
370+ runTaskkill = defaultRunTaskkill,
371+} = {},
344372) {
373+const signalOptions = { platform, runTaskkill };
345374const signalHandlers = new Map();
346375let forceKillTimer;
347376let parentSignalPending = false;
348377const forceCleanup = (signal) => {
349-signalGatewayProcessForParentExit(child, signal, killProcess);
350-if (process.platform !== "win32") {
351-signalGatewayProcessForParentExit(child, "SIGKILL", killProcess);
378+signalGatewayProcessForParentExit(child, signal, killProcess, signalOptions);
379+if (platform !== "win32") {
380+signalGatewayProcessForParentExit(child, "SIGKILL", killProcess, signalOptions);
352381}
353382};
354383const cleanupAndReraise = (signal) => {
355384parentSignalPending = true;
356-signalGatewayProcessForParentExit(child, signal, killProcess);
385+signalGatewayProcessForParentExit(child, signal, killProcess, signalOptions);
357386const finish = () => {
358387forceKillTimer = undefined;
359-signalGatewayProcessForParentExit(child, "SIGKILL", killProcess);
388+signalGatewayProcessForParentExit(child, "SIGKILL", killProcess, signalOptions);
360389processLike.kill?.(processLike.pid, signal);
361390};
362391// Signal handlers can give the detached gateway group one normal teardown
363392// grace window before re-raising; process exit cleanup cannot wait.
364-if (process.platform === "win32" || !gatewayProcessAliveForParentExit(child, killProcess)) {
393+if (
394+platform === "win32" ||
395+!gatewayProcessAliveForParentExit(child, killProcess, signalOptions)
396+) {
365397processLike.kill?.(processLike.pid, signal);
366398return;
367399}
@@ -393,31 +425,40 @@ export function installGatewayParentCleanup(
393425return removeHandlers;
394426}
395427396-async function waitForGatewayExit(child, timeoutMs, killProcess = defaultKillProcess) {
428+async function waitForGatewayExit(
429+child,
430+timeoutMs,
431+killProcess = defaultKillProcess,
432+signalOptions = {},
433+) {
397434const deadline = Date.now() + timeoutMs;
398435while (Date.now() <= deadline) {
399-if (!isGatewayProcessAlive(child, killProcess)) {
436+if (!isGatewayProcessAlive(child, killProcess, signalOptions)) {
400437return true;
401438}
402439await sleep(Math.min(25, Math.max(0, deadline - Date.now())));
403440}
404-return !isGatewayProcessAlive(child, killProcess);
441+return !isGatewayProcessAlive(child, killProcess, signalOptions);
405442}
406443407444/**
408445 * Stops the gateway with SIGTERM first and SIGKILL after the grace window.
409446 */
410447export async function stopGateway(child, options = {}) {
411-if (!isGatewayProcessAlive(child, options.killProcess)) {
448+const signalOptions = {
449+platform: options.platform ?? process.platform,
450+runTaskkill: options.runTaskkill ?? defaultRunTaskkill,
451+};
452+if (!isGatewayProcessAlive(child, options.killProcess, signalOptions)) {
412453return;
413454}
414455const killGraceMs = Math.max(0, options.killGraceMs ?? 1_500);
415456const forceKillGraceMs = Math.max(0, options.forceKillGraceMs ?? GATEWAY_FORCE_KILL_GRACE_MS);
416-signalGatewayProcess(child, "SIGTERM", options.killProcess);
417-const exited = await waitForGatewayExit(child, killGraceMs, options.killProcess);
457+signalGatewayProcess(child, "SIGTERM", options.killProcess, signalOptions);
458+const exited = await waitForGatewayExit(child, killGraceMs, options.killProcess, signalOptions);
418459if (!exited) {
419-signalGatewayProcess(child, "SIGKILL", options.killProcess);
420-await waitForGatewayExit(child, forceKillGraceMs, options.killProcess);
460+signalGatewayProcess(child, "SIGKILL", options.killProcess, signalOptions);
461+await waitForGatewayExit(child, forceKillGraceMs, options.killProcess, signalOptions);
421462}
422463}
423464此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。