From f251e69f51914cf0c7239a6a50de0e282309ded2 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Wed, 5 Aug 2026 23:58:30 +0700 Subject: [PATCH] fix(backend): force-exit failsafe so shutdown never hangs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit shutdown() awaited httpServer.close(), which waits for ALL open connections. A lingering WS/keep-alive socket left the process zombie forever after an uncaughtException (e.g. pg 'Connection terminated unexpectedly' to imrnes) — no exit, so systemd Restart=always could never revive it; /api/guilds returned 502 until manual restart. Add 10s force-exit timer in shutdown(); clear it on clean completion. --- services/backend/src/index.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/services/backend/src/index.ts b/services/backend/src/index.ts index d3f70f1..ce7d8d2 100644 --- a/services/backend/src/index.ts +++ b/services/backend/src/index.ts @@ -24,6 +24,15 @@ async function main() { async function shutdown(signal: string) { logger.info({ signal }, "Shutting down gracefully"); + // Failsafe: graceful shutdown must never hang the process forever. + // httpServer.close() waits for ALL open connections (including lingering + // WebSocket/keep-alive sockets), so on a stuck connection the process would + // otherwise sit zombie and systemd (Restart=always) can never revive it. + const forceExitTimer = setTimeout(() => { + logger.error({ signal }, "Graceful shutdown timed out; forcing exit"); + process.exit(1); + }, 10_000); + try { // 1. Stop accepting new HTTP connections if (httpServer) { @@ -54,9 +63,11 @@ async function shutdown(signal: string) { ); logger.info("Graceful shutdown completed"); + clearTimeout(forceExitTimer); process.exit(0); } catch (err) { logger.error({ err }, "Error during graceful shutdown"); + clearTimeout(forceExitTimer); process.exit(1); } }