


















@@ -102,27 +102,55 @@ function runSpawnCall(spawnCall, label) {
102102}
103103104104let forwardedSignal = null;
105+let forwardedSignalPids = [];
105106let forceKillTimer = null;
107+let forwardedSignalDrainTimer = null;
108+const clearForwardedSignalTimers = () => {
109+if (forceKillTimer) {
110+clearTimeout(forceKillTimer);
111+forceKillTimer = null;
112+}
113+if (forwardedSignalDrainTimer) {
114+clearInterval(forwardedSignalDrainTimer);
115+forwardedSignalDrainTimer = null;
116+}
117+};
118+const finishForwardedSignal = () => {
119+cleanupSignalHandlers();
120+process.kill(process.pid, forwardedSignal);
121+};
122+const waitForForwardedSignalChildren = () => {
123+if (!forwardedSignal || processTreeIsAlive(forwardedSignalPids)) {
124+return;
125+}
126+finishForwardedSignal();
127+};
106128// Keep UI dev children in the foreground process group for native TTY
107-// resize/job-control behavior. Forward direct wrapper shutdown signals.
129+// resize/job-control behavior. Forward wrapper shutdown signals to the
130+// captured child tree instead of using a detached process group.
108131const forwardedSignals = ["SIGTERM", "SIGHUP"];
109132const signalHandlers = new Map(
110133forwardedSignals.map((signal) => [
111134signal,
112135() => {
113-forwardedSignal ??= signal;
114-child.kill(signal);
115-forceKillTimer ??= setTimeout(() => child.kill("SIGKILL"), 5_000);
136+if (!forwardedSignal) {
137+forwardedSignal = signal;
138+forwardedSignalPids = collectChildProcessTreePids(child);
139+signalProcessTree(child, signal, forwardedSignalPids);
140+forwardedSignalDrainTimer = setInterval(waitForForwardedSignalChildren, 25);
141+forceKillTimer = setTimeout(() => {
142+signalProcessTree(child, "SIGKILL", forwardedSignalPids);
143+}, 5_000);
144+forceKillTimer.unref?.();
145+}
116146},
117147]),
118148);
119149const cleanupSignalHandlers = () => {
120150for (const [signal, handler] of signalHandlers) {
121151process.off(signal, handler);
122152}
123-if (forceKillTimer) {
124-clearTimeout(forceKillTimer);
125-}
153+clearForwardedSignalTimers();
126154};
127155for (const [signal, handler] of signalHandlers) {
128156process.on(signal, handler);
@@ -134,11 +162,11 @@ function runSpawnCall(spawnCall, label) {
134162process.exit(1);
135163});
136164child.on("exit", (code, signal) => {
137-cleanupSignalHandlers();
138165if (forwardedSignal) {
139-process.kill(process.pid, forwardedSignal);
166+waitForForwardedSignalChildren();
140167return;
141168}
169+cleanupSignalHandlers();
142170if (signal) {
143171process.kill(process.pid, signal);
144172return;
@@ -149,6 +177,66 @@ function runSpawnCall(spawnCall, label) {
149177});
150178}
151179180+function collectChildProcessTreePids(child) {
181+if (process.platform === "win32" || typeof child.pid !== "number") {
182+return typeof child.pid === "number" ? [child.pid] : [];
183+}
184+const ps = spawnSync("ps", ["-axo", "pid=,ppid="], { encoding: "utf8" });
185+if (ps.status !== 0) {
186+return [child.pid];
187+}
188+const childrenByParent = new Map();
189+for (const line of ps.stdout.split("\n")) {
190+const match = line.trim().match(/^(\d+)\s+(\d+)$/u);
191+if (!match) {
192+continue;
193+}
194+const pid = Number(match[1]);
195+const ppid = Number(match[2]);
196+const siblings = childrenByParent.get(ppid) ?? [];
197+siblings.push(pid);
198+childrenByParent.set(ppid, siblings);
199+}
200+const pids = [child.pid];
201+for (const parentPid of pids) {
202+for (const pid of childrenByParent.get(parentPid) ?? []) {
203+pids.push(pid);
204+}
205+}
206+return [...new Set(pids)];
207+}
208+209+function processTreeIsAlive(pids) {
210+return pids.some((pid) => {
211+try {
212+process.kill(pid, 0);
213+return true;
214+} catch (error) {
215+return error?.code === "EPERM";
216+}
217+});
218+}
219+220+function signalProcessTree(child, signal, pids) {
221+if (process.platform === "win32") {
222+child.kill(signal);
223+return;
224+}
225+if (pids.length === 0) {
226+child.kill(signal);
227+return;
228+}
229+for (const pid of pids.toReversed()) {
230+try {
231+process.kill(pid, signal);
232+} catch (error) {
233+if (error?.code !== "ESRCH") {
234+throw error;
235+}
236+}
237+}
238+}
239+152240function run(cmd, args) {
153241runSpawnCall(resolveSpawnCall(cmd, args), cmd);
154242}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。