feat(FN-5189): complete Step 3 — migrate supervised spawns

Fusion-Task-Id: FN-5189
Fusion-Task-Lineage: 4caa3f0a-af81-4c60-88e8-de229ed72e08
This commit is contained in:
Fusion (runfusion.ai)
2026-05-19 15:33:27 -07:00
committed by gsxdsm
parent 87ea63c7c3
commit 818db4a714
8 changed files with 33 additions and 38 deletions

View File

@@ -14,6 +14,8 @@ type ShutdownReason =
| { kind: "test"; label: string };
export interface SuperviseSpawnOptions extends Omit<SpawnOptions, "detached"> {
/** Override spawn for tests or alternate process factories. */
spawnImpl?: typeof spawn;
/**
* Grace period between SIGTERM and SIGKILL when the supervisor tears a child
* down because the parent is exiting or a lifetime limit expires.
@@ -246,11 +248,12 @@ export function superviseSpawn(
const {
killGraceMs = DEFAULT_KILL_GRACE_MS,
maxLifetimeMs = DEFAULT_MAX_LIFETIME_MS,
spawnImpl = spawn,
...spawnOptions
} = options;
const processGroup = usesProcessGroup();
const child = spawn(command, [...args], {
const child = spawnImpl(command, [...args], {
...spawnOptions,
detached: processGroup,
});

View File

@@ -1,4 +1,4 @@
import { spawn } from "node:child_process";
import { superviseSpawn } from "./process-supervisor.js";
export interface RunCommandOptions {
cwd?: string;
@@ -48,27 +48,18 @@ export function runCommandAsync(
let bufferExceeded = false;
let timedOut = false;
let forceKillTimer: NodeJS.Timeout | null = null;
const useProcessGroup = process.platform !== "win32";
const child = spawn(command, {
const supervised = superviseSpawn(command, [], {
cwd: options.cwd,
env: options.env,
detached: useProcessGroup,
shell: true,
stdio: ["ignore", "pipe", "pipe"],
maxLifetimeMs: (options.timeoutMs ?? 0) > 0 ? options.timeoutMs! + FORCE_KILL_DELAY_MS + 1_000 : undefined,
});
const child = supervised.child;
const signalProcessGroup = (signal: NodeJS.Signals): void => {
if (!child.pid) return;
try {
if (useProcessGroup) {
process.kill(-child.pid, signal);
} else {
child.kill(signal);
}
} catch {
// The command may already have exited and cleaned up its process group.
}
supervised.kill(signal);
};
const scheduleForceKill = (delayMs: number): void => {