From 0bf62e83d226bcb9e3f4e8229094d2fcd930d202 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 30 May 2026 10:01:22 -0700 Subject: [PATCH] fix: terminate websocket clients during shutdown (#3783) --- src/main/runtime/rpc/ws-transport.test.ts | 21 +++++++++++++++++++++ src/main/runtime/rpc/ws-transport.ts | 4 +++- 2 files changed, 24 insertions(+), 1 deletion(-) 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() }