Revert "fix: prevent orphaned gateway processes on Windows causing port conflcts and UI freeze (#570)"

This reverts commit 2dc1070213.
This commit is contained in:
paisley
2026-03-18 18:28:34 +08:00
Unverified
parent b27815b394
commit dbc9972fb8
2 changed files with 16 additions and 52 deletions

View File

@@ -32,33 +32,16 @@ export async function terminateOwnedGatewayProcess(child: Electron.UtilityProces
const pid = child.pid; const pid = child.pid;
logger.info(`Sending kill to Gateway process (pid=${pid ?? 'unknown'})`); logger.info(`Sending kill to Gateway process (pid=${pid ?? 'unknown'})`);
// On Windows, use taskkill /F /T to kill the entire process tree.
// child.kill() only terminates the direct utilityProcess; grandchild
// processes (Python/uv) survive and keep port 18789 occupied.
if (process.platform === 'win32' && pid) {
import('child_process').then((cp) => {
cp.exec(`taskkill /F /PID ${pid} /T`, { timeout: 5000, windowsHide: true }, () => {
// best-effort; fall through to timeout if taskkill fails
});
}).catch(() => { /* ignore */ });
} else {
try { try {
child.kill(); child.kill();
} catch { } catch {
// ignore if already exited // ignore if already exited
} }
}
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
if (!exited) { if (!exited) {
logger.warn(`Gateway did not exit in time, force-killing (pid=${pid ?? 'unknown'})`); logger.warn(`Gateway did not exit in time, force-killing (pid=${pid ?? 'unknown'})`);
if (pid) { if (pid) {
if (process.platform === 'win32') {
import('child_process').then((cp) => {
cp.exec(`taskkill /F /PID ${pid} /T`, { timeout: 5000, windowsHide: true }, () => {});
}).catch(() => {});
} else {
try { try {
process.kill(pid, 'SIGKILL'); process.kill(pid, 'SIGKILL');
} catch { } catch {
@@ -66,7 +49,6 @@ export async function terminateOwnedGatewayProcess(child: Electron.UtilityProces
} }
} }
} }
}
resolve(); resolve();
}, 5000); }, 5000);
@@ -244,9 +226,6 @@ export async function findExistingGatewayProcess(options: {
const pids = await getListeningProcessIds(port); const pids = await getListeningProcessIds(port);
if (pids.length > 0 && (!ownedPid || !pids.includes(String(ownedPid)))) { if (pids.length > 0 && (!ownedPid || !pids.includes(String(ownedPid)))) {
await terminateOrphanedProcessIds(port, pids); await terminateOrphanedProcessIds(port, pids);
// Verify the port is actually free after killing orphans.
// On Windows, TCP TIME_WAIT can hold the port for up to 120s.
await waitForPortFree(port, 10000);
return null; return null;
} }
} catch (err) { } catch (err) {

View File

@@ -459,30 +459,15 @@ if (gotTheLock) {
} }
}); });
let gatewayCleanedUp = false; app.on('before-quit', () => {
app.on('before-quit', (event) => {
setQuitting(); setQuitting();
// On first before-quit, block the quit so we can await gateway cleanup.
// On Windows, fire-and-forget leaves orphaned Python/uv processes that
// hold port 18789, causing port conflicts on next launch.
if (!gatewayCleanedUp) {
gatewayCleanedUp = true;
event.preventDefault();
hostEventBus.closeAll(); hostEventBus.closeAll();
hostApiServer?.close(); hostApiServer?.close();
// Fire-and-forget: do not await gatewayManager.stop() here.
const stopPromise = gatewayManager.stop().catch((err) => { // Awaiting inside before-quit can stall Electron's quit sequence.
void gatewayManager.stop().catch((err) => {
logger.warn('gatewayManager.stop() error during quit:', err); logger.warn('gatewayManager.stop() error during quit:', err);
}); });
const timeoutPromise = new Promise<void>((resolve) => setTimeout(resolve, 5000));
void Promise.race([stopPromise, timeoutPromise]).then(() => {
app.exit(0);
});
}
}); });
} }