From 9b739fd8e4d0f61ed2675f51f10398b7c534c648 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Thu, 12 Mar 2026 14:47:13 -0700 Subject: [PATCH] Simplify killTree with single ps call --- src/run.ts | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/src/run.ts b/src/run.ts index d0f9beb..b5fbcc6 100644 --- a/src/run.ts +++ b/src/run.ts @@ -28,30 +28,27 @@ type RunOptions = { onCommand?: (cmd: Command) => void } -function getDescendants(pid: number): number[] { - try { - const result = Bun.spawnSync(["ps", "-o", "pid=", "--ppid", String(pid)]) - const output = new TextDecoder().decode(result.stdout) - const children = output.trim().split("\n").filter(Boolean).map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n)) - const all: number[] = [] - for (const child of children) { - all.push(...getDescendants(child)) - all.push(child) - } - return all - } catch { - return [] - } -} - function killTree(pid: number): void { - const descendants = getDescendants(pid) - // Kill process group first + // Find any processes that escaped the process group (e.g. via setsid) + // using a single cross-platform ps call, then kill them before the group. + try { + const result = Bun.spawnSync(["ps", "-eo", "pid,pgid"]) + const output = result.stdout.toString() + const pgid = String(pid) + const escapees: number[] = [] + for (const line of output.split("\n")) { + const parts = line.trim().split(/\s+/) + if (parts[1] === pgid) { + const p = parseInt(parts[0]!, 10) + if (!isNaN(p) && p !== pid) escapees.push(p) + } + } + for (const p of escapees) { + try { process.kill(p, "SIGKILL") } catch {} + } + } catch {} + // Kill the process group try { process.kill(-pid, "SIGKILL") } catch {} - // Then kill any descendants that escaped the process group - for (const d of descendants) { - try { process.kill(d, "SIGKILL") } catch {} - } } const SENTINEL_PREFIX = "__SHOUT_SENTINEL_"