From 95d9074b16ed56af9f29ab3295030967ba9a5f56 Mon Sep 17 00:00:00 2001 From: trexfr-ops Date: Wed, 19 Aug 2026 15:48:53 +0000 Subject: [PATCH 1/2] fix(server): add graceful shutdown on SIGTERM and SIGINT to reset connected characters --- server/src/server.ts | 68 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/server/src/server.ts b/server/src/server.ts index 69dc45af..cf3c23d4 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -943,9 +943,67 @@ createDynamicScheduler( createDynamicScheduler( () => FLOOR_ITEM_SWEEP_CHECK_MS, - function () { - return processFloorItemSweepTick(Date.now()); - }, -); - void saveOnlineStatsSnapshot(); + +let isShuttingDown = false; + +async function gracefulShutdown(signal: string) { + if (isShuttingDown) { + return; + } + isShuttingDown = true; + + console.log(`[Servidor] Señal ${signal} recibida. Iniciando apagado graceful...`); + + // Timeout safety fallback so process exits even if API is unresponsive + const forceExitTimeout = setTimeout(() => { + console.error("[Servidor] Timeout en apagado graceful. Forzando salida."); + process.exit(1); + }, 5000); + if (typeof forceExitTimeout.unref === "function") { + forceExitTimeout.unref(); + } + + try { + // 1. Notificar a los clientes conectados + try { + if (handleProtocol && typeof handleProtocol.consoleToAll === "function") { + handleProtocol.consoleToAll("[Servidor] El servidor se está apagando.", "#E69500", 1, 0); + } + } catch (e) { + console.error("[Servidor] Error al notificar clientes durante apagado:", e); + } + + // 2. Desmarcar personajes conectados en la base de datos + try { + const resetCharactersResponse = (await funct.fetchUrl("/internal/characters/reset-connected", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: vars.tokenAuth, + }, + })) as { updated: number }; + + console.log( + `[Servidor] Personajes desmarcados al apagar (${signal}): ${resetCharactersResponse?.updated ?? 0}.`, + ); + } catch (e) { + console.error("[Servidor] Error al desmarcar personajes durante apagado:", e); + } + + // 3. Cerrar servidores WebSocket y HTTP + if (wsServer && typeof wsServer.close === "function") { + wsServer.close(); + } + if (httpServer && typeof httpServer.close === "function") { + httpServer.close(); + } + } finally { + clearTimeout(forceExitTimeout); + process.exit(0); + } +} + +process.on("SIGTERM", () => void gracefulShutdown("SIGTERM")); +process.on("SIGINT", () => void gracefulShutdown("SIGINT")); + From 6b850260d0f95ff277208c97454400a3b5d9813a Mon Sep 17 00:00:00 2001 From: trexfr-ops Date: Wed, 19 Aug 2026 16:35:57 +0000 Subject: [PATCH 2/2] fix(server): restore floor item sweep scheduler and await socket close callbacks in gracefulShutdown --- server/src/server.ts | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/server/src/server.ts b/server/src/server.ts index cf3c23d4..3df0f1ca 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -943,6 +943,11 @@ createDynamicScheduler( createDynamicScheduler( () => FLOOR_ITEM_SWEEP_CHECK_MS, + function () { + return processFloorItemSweepTick(Date.now()); + }, +); + void saveOnlineStatsSnapshot(); let isShuttingDown = false; @@ -991,13 +996,23 @@ async function gracefulShutdown(signal: string) { console.error("[Servidor] Error al desmarcar personajes durante apagado:", e); } - // 3. Cerrar servidores WebSocket y HTTP - if (wsServer && typeof wsServer.close === "function") { - wsServer.close(); - } - if (httpServer && typeof httpServer.close === "function") { - httpServer.close(); - } + // 3. Cerrar servidores WebSocket y HTTP ordenadamente + await Promise.all([ + new Promise((resolve) => { + if (wsServer && typeof wsServer.close === "function") { + wsServer.close(() => resolve()); + } else { + resolve(); + } + }), + new Promise((resolve) => { + if (httpServer && typeof httpServer.close === "function") { + httpServer.close(() => resolve()); + } else { + resolve(); + } + }), + ]); } finally { clearTimeout(forceExitTimeout); process.exit(0);