From 4adf50dbcb6af4bd1e2a9a27371556e7faaaaada Mon Sep 17 00:00:00 2001 From: backryun Date: Sun, 16 Aug 2026 01:51:11 +0900 Subject: [PATCH] fix(ci): clean up Windows packaged smoke process trees (#10453) --- scripts/dev/smoke-electron-packaged.mjs | 31 +++++++++++++++++------- tests/unit/electron-smoke-script.test.ts | 24 ++++++++++++++++++ 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/scripts/dev/smoke-electron-packaged.mjs b/scripts/dev/smoke-electron-packaged.mjs index 473cbf6303..03717912ff 100644 --- a/scripts/dev/smoke-electron-packaged.mjs +++ b/scripts/dev/smoke-electron-packaged.mjs @@ -255,20 +255,33 @@ async function signalProcessTree(child, signal) { } } -async function stopApp(child) { +export async function stopApp( + child, + { + currentPlatform = platform(), + signalProcessTreeFn = signalProcessTree, + waitForProcessTreeExitFn = waitForProcessTreeExit, + } = {} +) { if (!child.pid) return; - await signalProcessTree(child, "SIGTERM"); - await waitForProcessTreeExit(child, 5_000); + // On Windows, terminating only the direct Electron process can orphan the + // packaged server when the parent exits before the follow-up liveness check. + // Kill the process tree in one operation while the root PID is still valid. + if (currentPlatform === "win32") { + await signalProcessTreeFn(child, "SIGKILL"); + await waitForProcessTreeExitFn(child, 2_000); + return; + } - const isStillRunning = - platform() === "win32" - ? child.exitCode === null && child.signalCode === null - : isProcessGroupAlive(child.pid); + await signalProcessTreeFn(child, "SIGTERM"); + await waitForProcessTreeExitFn(child, 5_000); + + const isStillRunning = isProcessGroupAlive(child.pid); if (isStillRunning) { - await signalProcessTree(child, "SIGKILL"); - await waitForProcessTreeExit(child, 2_000); + await signalProcessTreeFn(child, "SIGKILL"); + await waitForProcessTreeExitFn(child, 2_000); } } diff --git a/tests/unit/electron-smoke-script.test.ts b/tests/unit/electron-smoke-script.test.ts index f2fdcbe3d2..9f3bea9f8e 100644 --- a/tests/unit/electron-smoke-script.test.ts +++ b/tests/unit/electron-smoke-script.test.ts @@ -5,6 +5,7 @@ import { buildSmokeEnv, FATAL_LOG_PATTERNS, LINUX_EXECUTABLE_NAMES, + stopApp, } from "../../scripts/dev/smoke-electron-packaged.mjs"; test("electron smoke discovers the default Linux executable name", () => { @@ -47,3 +48,26 @@ test("electron smoke treats Electron process errors as fatal startup logs", () = ); } }); + +test("electron smoke force-terminates the Windows process tree before the parent can exit", async () => { + const signals: string[] = []; + const waits: number[] = []; + const child = { + pid: 4242, + exitCode: 0, + signalCode: null, + }; + + await stopApp(child, { + currentPlatform: "win32", + signalProcessTreeFn: async (_child, signal) => { + signals.push(signal); + }, + waitForProcessTreeExitFn: async (_child, timeoutMs) => { + waits.push(timeoutMs); + }, + }); + + assert.deepEqual(signals, ["SIGKILL"]); + assert.deepEqual(waits, [2_000]); +});