diff --git a/electron/gateway/supervisor.ts b/electron/gateway/supervisor.ts index f8da21faa..04391d944 100644 --- a/electron/gateway/supervisor.ts +++ b/electron/gateway/supervisor.ts @@ -32,38 +32,20 @@ export async function terminateOwnedGatewayProcess(child: Electron.UtilityProces const pid = child.pid; 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 { - child.kill(); - } catch { - // ignore if already exited - } + 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) { - if (process.platform === 'win32') { - import('child_process').then((cp) => { - cp.exec(`taskkill /F /PID ${pid} /T`, { timeout: 5000, windowsHide: true }, () => {}); - }).catch(() => {}); - } else { - try { - process.kill(pid, 'SIGKILL'); - } catch { - // ignore - } + try { + process.kill(pid, 'SIGKILL'); + } catch { + // ignore } } } @@ -244,9 +226,6 @@ export async function findExistingGatewayProcess(options: { const pids = await getListeningProcessIds(port); if (pids.length > 0 && (!ownedPid || !pids.includes(String(ownedPid)))) { 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; } } catch (err) { diff --git a/electron/main/index.ts b/electron/main/index.ts index 5a4607ac5..3748461c6 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -459,30 +459,15 @@ if (gotTheLock) { } }); - let gatewayCleanedUp = false; - - app.on('before-quit', (event) => { + app.on('before-quit', () => { 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(); - hostApiServer?.close(); - - const stopPromise = gatewayManager.stop().catch((err) => { - logger.warn('gatewayManager.stop() error during quit:', err); - }); - const timeoutPromise = new Promise((resolve) => setTimeout(resolve, 5000)); - - void Promise.race([stopPromise, timeoutPromise]).then(() => { - app.exit(0); - }); - } + hostEventBus.closeAll(); + hostApiServer?.close(); + // Fire-and-forget: do not await gatewayManager.stop() here. + // Awaiting inside before-quit can stall Electron's quit sequence. + void gatewayManager.stop().catch((err) => { + logger.warn('gatewayManager.stop() error during quit:', err); + }); }); }