fix: harden gateway process shutdown lifecycle

Co-authored-by: Haze <hazeone@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-03-18 12:09:25 +00:00
committed by Haze
parent 6b0400e3c3
commit 4d6a60fa77
9 changed files with 285 additions and 13 deletions

View File

@@ -241,6 +241,7 @@ export class GatewayManager extends EventEmitter {
onConnectedToExistingGateway: () => {
this.ownsProcess = false;
this.setStatus({ pid: undefined });
logger.info(`Gateway manager attached to external process on port ${this.status.port} (ownsProcess=false)`);
this.startHealthCheck();
},
waitForPortFree: async (port) => {
@@ -714,6 +715,7 @@ export class GatewayManager extends EventEmitter {
this.process = child;
this.ownsProcess = true;
logger.debug(`Gateway manager now owns process pid=${child.pid ?? 'unknown'}`);
this.lastSpawnSummary = lastSpawnSummary;
}

View File

@@ -24,6 +24,13 @@ export function warmupManagedPythonReadiness(): void {
export async function terminateOwnedGatewayProcess(child: Electron.UtilityProcess): Promise<void> {
let exited = false;
const terminateWindowsProcessTree = async (pid: number): Promise<void> => {
const cp = await import('child_process');
await new Promise<void>((resolve) => {
cp.exec(`taskkill /F /PID ${pid} /T`, { timeout: 5000, windowsHide: true }, () => resolve());
});
};
await new Promise<void>((resolve) => {
child.once('exit', () => {
exited = true;
@@ -32,20 +39,33 @@ export async function terminateOwnedGatewayProcess(child: Electron.UtilityProces
const pid = child.pid;
logger.info(`Sending kill to Gateway process (pid=${pid ?? 'unknown'})`);
try {
child.kill();
} catch {
// ignore if already exited
if (process.platform === 'win32' && pid) {
void terminateWindowsProcessTree(pid).catch((err) => {
logger.warn(`Windows process-tree kill failed for Gateway pid=${pid}:`, err);
});
} else {
try {
child.kill();
} catch {
// ignore if already exited
}
}
const timeout = setTimeout(() => {
if (!exited) {
logger.warn(`Gateway did not exit in time, force-killing (pid=${pid ?? 'unknown'})`);
if (pid) {
try {
process.kill(pid, 'SIGKILL');
} catch {
// ignore
if (process.platform === 'win32') {
void terminateWindowsProcessTree(pid).catch((err) => {
logger.warn(`Forced Windows process-tree kill failed for Gateway pid=${pid}:`, err);
});
} else {
try {
process.kill(pid, 'SIGKILL');
} catch {
// ignore
}
}
}
}
@@ -226,6 +246,9 @@ export async function findExistingGatewayProcess(options: {
const pids = await getListeningProcessIds(port);
if (pids.length > 0 && (!ownedPid || !pids.includes(String(ownedPid)))) {
await terminateOrphanedProcessIds(port, pids);
if (process.platform === 'win32') {
await waitForPortFree(port, 10000);
}
return null;
}
} catch (err) {