fix(server): prevent shutdown hang by forcefully terminating connections

Use client.terminate() instead of client.close() to avoid waiting for
unresponsive clients to acknowledge the WebSocket close handshake.

Add a 2-second safety timeout that closes the HTTP listener, destroys
lingering sockets, and resolves the promise so session_shutdown does not
block pi from exiting.
This commit is contained in:
Yejun Su 2026-03-19 14:37:31 +08:00
parent 37dc2b2f1e
commit ad08d297a5
No known key found for this signature in database
GPG Key ID: AD03A563F321CA44
1 changed files with 22 additions and 3 deletions

View File

@ -226,15 +226,34 @@ export function startServer(pi: ExtensionAPI, ctx: ExtensionContext): Promise<Re
resolve({ resolve({
broadcast, broadcast,
stop: () => stop: () =>
new Promise((res) => { new Promise<void>((res) => {
// Forcefully kill all WebSocket clients — terminate() sends no
// close frame and doesn't wait for the remote end to acknowledge,
// so it can't hang on an unresponsive client.
for (const client of clients) { for (const client of clients) {
try { try {
client.close(); client.terminate();
} catch { } catch {
/* ignore */ /* ignore */
} }
} }
wss.close(() => httpServer.close(() => res())); clients.clear();
// Safety timeout — if wss/http shutdown callbacks never fire
// (e.g. lingering keep-alive sockets), resolve anyway so the
// session_shutdown handler doesn't block pi from exiting.
const timeout = setTimeout(() => {
httpServer.close(() => {});
httpServer.closeAllConnections?.();
res();
}, 2000);
wss.close(() =>
httpServer.close(() => {
clearTimeout(timeout);
res();
}),
);
}), }),
clientCount: () => clients.size, clientCount: () => clients.size,
onClientChange: (cb: () => void) => { clientChangeListeners.push(cb); }, onClientChange: (cb: () => void) => { clientChangeListeners.push(cb); },