fix(gateway): terminate owned process before retry to prevent port conflict on Windows (#724)

This commit is contained in:
写了吗?
2026-04-02 16:11:03 +08:00
committed by GitHub
Unverified
parent d3112f28d8
commit 560ae95611
3 changed files with 67 additions and 6 deletions

View File

@@ -18,13 +18,15 @@ type StartupHooks = {
findExistingGateway: (port: number) => Promise<ExistingGatewayInfo | null>;
connect: (port: number, externalToken?: string) => Promise<void>;
onConnectedToExistingGateway: () => void;
waitForPortFree: (port: number) => Promise<void>;
waitForPortFree: (port: number, signal?: AbortSignal) => Promise<void>;
startProcess: () => Promise<void>;
waitForReady: (port: number) => Promise<void>;
onConnectedToManagedGateway: () => void;
runDoctorRepair: () => Promise<boolean>;
onDoctorRepairSuccess: () => void;
delay: (ms: number) => Promise<void>;
/** Called before a retry to terminate the previously spawned process if still running */
terminateOwnedProcess?: () => Promise<void>;
};
export async function runGatewayStartupSequence(hooks: StartupHooks): Promise<void> {
@@ -97,6 +99,34 @@ export async function runGatewayStartupSequence(hooks: StartupHooks): Promise<vo
if (recoveryAction === 'retry') {
logger.warn(`Transient start error: ${String(error)}. Retrying... (${startAttempts}/${maxStartAttempts})`);
await hooks.delay(1000);
// Terminate the previously spawned process before retrying so it doesn't
// hold the port and cause another handshake failure.
if (hooks.terminateOwnedProcess) {
await hooks.terminateOwnedProcess().catch((err) => {
logger.warn('Failed to terminate owned process before retry:', err);
});
}
hooks.assertLifecycle('start/retry-pre-port-wait');
// Wait for port to become free before retrying (handles lingering processes).
// Use a short-polling AbortController so that a superseding stop()/restart()
// can cancel the wait promptly instead of blocking for the full 30s timeout.
if (hooks.shouldWaitForPortFree) {
const abortController = new AbortController();
// Poll lifecycle every 500ms and abort the port-wait if superseded
const lifecyclePollInterval = setInterval(() => {
try {
hooks.assertLifecycle('start/retry-port-wait-poll');
} catch {
abortController.abort();
}
}, 500);
try {
await hooks.waitForPortFree(hooks.port, abortController.signal);
} finally {
clearInterval(lifecyclePollInterval);
}
}
hooks.assertLifecycle('start/retry-post-port-wait');
continue;
}