From 8a382d45ae7f5b8664a9901904049c8724828353 Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Wed, 24 Jun 2026 01:43:34 +0800 Subject: [PATCH] fix(v03): forward sigterm from env reuse launcher (#2002) --- .../launcher/hwlab-env-reuse-launcher.ts | 54 ++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/deploy/runtime/launcher/hwlab-env-reuse-launcher.ts b/deploy/runtime/launcher/hwlab-env-reuse-launcher.ts index 5d7a099d..4aa17d42 100644 --- a/deploy/runtime/launcher/hwlab-env-reuse-launcher.ts +++ b/deploy/runtime/launcher/hwlab-env-reuse-launcher.ts @@ -1,4 +1,4 @@ -import { spawnSync } from "node:child_process"; +import { spawn, spawnSync } from "node:child_process"; import { chmodSync, existsSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; import path from "node:path"; @@ -89,6 +89,10 @@ function installHwpodAlias(commandName: string, relativeCli: string): void { } function run(command: string, args: string[], cwd: string, options: { replace?: boolean } = {}): void { + if (options.replace) { + runReplacingProcess(command, args, cwd); + return; + } const result = spawnSync(command, args, { cwd, env: process.env, @@ -97,10 +101,56 @@ function run(command: string, args: string[], cwd: string, options: { replace?: if (result.error) fail(`${command} failed to start: ${result.error.message}`); if (result.signal) process.kill(process.pid, result.signal); const code = result.status ?? 0; - if (options.replace) process.exit(code); if (code !== 0) process.exit(code); } +function runReplacingProcess(command: string, args: string[], cwd: string): void { + const child = spawn(command, args, { + cwd, + env: process.env, + stdio: "inherit", + detached: true + }); + let settled = false; + const signalHandlers: Array<{ signal: string, handler: () => void }> = []; + const cleanup = () => { + for (const { signal, handler } of signalHandlers.splice(0)) { + process.off(signal, handler); + } + }; + const forwardSignal = (signal: string) => { + if (!child.pid) return; + try { + process.kill(-child.pid, signal); + } catch { + try { child.kill(signal); } catch {} + } + }; + for (const signal of ["SIGINT", "SIGTERM"]) { + const handler = () => forwardSignal(signal); + process.on(signal, handler); + signalHandlers.push({ signal, handler }); + } + child.on("error", (error) => { + if (settled) return; + settled = true; + cleanup(); + fail(`${command} failed to start: ${error.message}`); + }); + child.on("exit", (code, signal) => { + if (settled) return; + settled = true; + cleanup(); + if (signal) process.exit(signalExitCode(signal)); + process.exit(code ?? 0); + }); +} + +function signalExitCode(signal: string): number { + const signals: Record = { SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGTERM: 15 }; + return 128 + (signals[signal] ?? 1); +} + function fail(message: string): never { process.stderr.write(`${JSON.stringify({ status: "failed", error: "hwlab_env_reuse_launcher_failed", message })}\n`); process.exit(1);