diff --git a/src/main/runtime/rpc/ws-transport.test.ts b/src/main/runtime/rpc/ws-transport.test.ts index 72af8f18590..d93fe6ace10 100644 --- a/src/main/runtime/rpc/ws-transport.test.ts +++ b/src/main/runtime/rpc/ws-transport.test.ts @@ -316,6 +316,27 @@ describe('WebSocketTransport', () => { await transport.stop() }) + it('does not wait for an unresponsive client close handshake during stop', async () => { + const { transport } = await createTransport() + await transport.start() + const ws = await connectWs(transport) + const underlying = (ws as unknown as { _socket: { pause: () => void } })._socket + underlying.pause() + + const stopPromise = transport.stop() + const outcome = await Promise.race([ + stopPromise.then(() => 'stopped' as const), + new Promise<'pending'>((resolve) => setTimeout(() => resolve('pending'), 100)) + ]) + + if (outcome === 'pending') { + ws.terminate() + await stopPromise + } + + expect(outcome).toBe('stopped') + }) + it('falls back to OS-assigned port when preferred port is in use', async () => { const { transport: first } = await createTransport() await first.start() diff --git a/src/main/runtime/rpc/ws-transport.ts b/src/main/runtime/rpc/ws-transport.ts index 07ccc095265..98166c8e988 100644 --- a/src/main/runtime/rpc/ws-transport.ts +++ b/src/main/runtime/rpc/ws-transport.ts @@ -251,7 +251,9 @@ export class WebSocketTransport implements RpcTransport { if (wss) { for (const client of wss.clients) { - client.close(1001, 'Server shutting down') + // Why: stop() is a teardown path. A half-open mobile socket may never + // answer a graceful close frame, which keeps httpServer.close pending. + client.terminate() } wss.close() }