
























11// Gateway Bench Child script supports OpenClaw repository automation.
2-import type { ChildProcessWithoutNullStreams } from "node:child_process";
2+import { spawnSync, type ChildProcessWithoutNullStreams } from "node:child_process";
3344const TEARDOWN_GRACE_MS = 2_000;
55const TEARDOWN_KILL_GRACE_MS = 1_000;
@@ -14,6 +14,13 @@ export type StopChildResult = ChildExit & {
1414exitedBeforeTeardown: boolean;
1515};
161617+export type StopChildOptions = {
18+killGraceMs?: number;
19+platform?: NodeJS.Platform;
20+runTaskkill?: typeof spawnSync;
21+teardownGraceMs?: number;
22+};
23+1724export function delay(ms: number): Promise<void> {
1825return new Promise((resolve) => {
1926setTimeout(resolve, ms);
@@ -22,10 +29,14 @@ export function delay(ms: number): Promise<void> {
22292330export async function stopChild(
2431child: ChildProcessWithoutNullStreams,
25-options: { killGraceMs?: number; teardownGraceMs?: number } = {},
32+options: StopChildOptions = {},
2633): Promise<StopChildResult> {
2734const teardownGraceMs = options.teardownGraceMs ?? TEARDOWN_GRACE_MS;
2835const killGraceMs = options.killGraceMs ?? TEARDOWN_KILL_GRACE_MS;
36+const processTreeOptions = {
37+platform: options.platform ?? process.platform,
38+runTaskkill: options.runTaskkill ?? spawnSync,
39+};
2940let observedExit: ChildExit | null = null;
3041const directExit = (): ChildExit | null =>
3142observedExit ??
@@ -34,34 +45,34 @@ export async function stopChild(
3445 : null);
3546const currentExit = (): ChildExit | null => {
3647const exit = directExit();
37-if (exit == null || isProcessTreeAlive(child)) {
48+if (exit == null || isProcessTreeAlive(child, processTreeOptions)) {
3849return null;
3950}
4051return exit;
4152};
4253const waitForProcessTreeExit = async (ms: number): Promise<boolean> => {
4354const deadlineAt = Date.now() + ms;
4455while (Date.now() < deadlineAt) {
45-if (!isProcessTreeAlive(child)) {
56+if (!isProcessTreeAlive(child, processTreeOptions)) {
4657return true;
4758}
4859await delay(Math.min(EXIT_POLL_MS, deadlineAt - Date.now()));
4960}
50-return !isProcessTreeAlive(child);
61+return !isProcessTreeAlive(child, processTreeOptions);
5162};
5263const cleanupExitedProcessTree = async (
5364exit: ChildExit,
5465exitedBeforeTeardown: boolean,
5566): Promise<StopChildResult> => {
56-if (!isProcessTreeAlive(child)) {
67+if (!isProcessTreeAlive(child, processTreeOptions)) {
5768return { ...exit, exitedBeforeTeardown };
5869}
59-const sentTeardownSignal = killProcessTree(child, "SIGTERM");
70+const sentTeardownSignal = killProcessTree(child, "SIGTERM", processTreeOptions);
6071if (sentTeardownSignal) {
6172await waitForProcessTreeExit(teardownGraceMs);
6273}
63-if (sentTeardownSignal && isProcessTreeAlive(child)) {
64-killProcessTree(child, "SIGKILL");
74+if (sentTeardownSignal && isProcessTreeAlive(child, processTreeOptions)) {
75+killProcessTree(child, "SIGKILL", processTreeOptions);
6576await waitForProcessTreeExit(killGraceMs);
6677}
6778if (!sentTeardownSignal) {
@@ -106,7 +117,7 @@ export async function stopChild(
106117return await cleanupExitedProcessTree(queuedExit, true);
107118}
108119109-const sentTeardownSignal = killProcessTree(child, "SIGTERM");
120+const sentTeardownSignal = killProcessTree(child, "SIGTERM", processTreeOptions);
110121const gracefulExit = await waitForExit(teardownGraceMs);
111122if (gracefulExit != null) {
112123return { ...gracefulExit, exitedBeforeTeardown: !sentTeardownSignal };
@@ -121,7 +132,7 @@ export async function stopChild(
121132return { exitCode: null, exitedBeforeTeardown: true, signal: null };
122133}
123134124-killProcessTree(child, "SIGKILL");
135+killProcessTree(child, "SIGKILL", processTreeOptions);
125136const killedExit = await waitForExit(killGraceMs);
126137const finalExit = killedExit ?? currentExit();
127138if (finalExit != null) {
@@ -139,8 +150,11 @@ function releaseUnsettledChild(child: ChildProcessWithoutNullStreams): void {
139150child.unref();
140151}
141152142-function isProcessTreeAlive(child: ChildProcessWithoutNullStreams): boolean {
143-if (process.platform === "win32" || child.pid === undefined) {
153+function isProcessTreeAlive(
154+child: ChildProcessWithoutNullStreams,
155+{ platform = process.platform }: Pick<StopChildOptions, "platform"> = {},
156+): boolean {
157+if (platform === "win32" || child.pid === undefined) {
144158return false;
145159}
146160try {
@@ -156,14 +170,28 @@ function isProcessStillExistsError(error: unknown): boolean {
156170return code === "EPERM";
157171}
158172159-function killProcessTree(child: ChildProcessWithoutNullStreams, signal: NodeJS.Signals): boolean {
160-if (process.platform !== "win32" && child.pid !== undefined) {
173+function killProcessTree(
174+child: ChildProcessWithoutNullStreams,
175+signal: NodeJS.Signals,
176+{ platform = process.platform, runTaskkill = spawnSync }: StopChildOptions = {},
177+): boolean {
178+if (platform !== "win32" && child.pid !== undefined) {
161179try {
162180process.kill(-child.pid, signal);
163181return true;
164182} catch {
165183// Fall back to the direct child below.
166184}
167185}
186+if (platform === "win32" && child.pid !== undefined) {
187+const args = ["/PID", String(child.pid), "/T"];
188+if (signal === "SIGKILL") {
189+args.push("/F");
190+}
191+const result = runTaskkill("taskkill", args, { stdio: "ignore" });
192+if (!result.error && result.status === 0) {
193+return true;
194+}
195+}
168196return child.kill(signal);
169197}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。