diff --git a/docs/development/CENTRAL.md b/docs/development/CENTRAL.md index e108984..84ed48c 100644 --- a/docs/development/CENTRAL.md +++ b/docs/development/CENTRAL.md @@ -33,3 +33,4 @@ - [Multi-Expansion Selection](./devlog/2025-12-16-230500_multi_expansion_selection.md): Completed. Implemented searchable multi-select interface for "From Expansion" pack generation, allowing mixed-set drafts. - [Game Type Filter](./devlog/2025-12-16-231000_game_type_filter.md): Completed. Added Paper/Digital filter to the expansion selection list. - [Incremental Caching](./devlog/2025-12-16-233000_incremental_caching.md): Completed. Refactored data fetching to cache cards to the server incrementally per set, preventing PayloadTooLarge errors. +- [Server Graceful Shutdown Fix](./devlog/2025-12-17-002000_server_shutdown_fix.md): Completed. Implemented signal handling to ensure clean process exit on Ctrl+C. diff --git a/docs/development/devlog/2025-12-17-002000_server_shutdown_fix.md b/docs/development/devlog/2025-12-17-002000_server_shutdown_fix.md new file mode 100644 index 0000000..7882a43 --- /dev/null +++ b/docs/development/devlog/2025-12-17-002000_server_shutdown_fix.md @@ -0,0 +1,17 @@ +# Server Graceful Shutdown Fix + +## Context +The user reported that the application process was not exiting clean (hanging for >5s) after pressing Ctrl+C. This indicated active handles (like intervals or open sockets) were preventing the Node.js process from terminating effectively. + +## Changes +Modified `src/server/index.ts` to implement a proper graceful shutdown mechanism: +1. **Interval Management**: Captured the global draft timer `setInterval` ID into a variable `draftInterval`. +2. **Shutdown Handler**: Created a `gracefulShutdown` function that: + - Clears the `draftInterval`. + - Closes the Socket.IO server (`io.close()`). + - Closes the HTTP server (`httpServer.close()`), waiting for existing connections to close, then exits with code 0. + - Sets a 10-second timeout to force exit with code 1 if connections don't close in time. +3. **Signal Listeners**: Attached `gracefulShutdown` to `SIGINT` and `SIGTERM` events. + +## Impact +The server should now exit immediately and cleanly when stopped via the terminal, ensuring no zombie processes or port conflicts during development restarts. diff --git a/src/server/index.ts b/src/server/index.ts index 1bab0d8..c35eb79 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -155,7 +155,7 @@ app.post('/api/packs/generate', async (req: Request, res: Response) => { }); // Global Draft Timer Loop -setInterval(() => { +const draftInterval = setInterval(() => { const updates = draftManager.checkTimers(); updates.forEach(({ roomId, draft }) => { io.to(roomId).emit('draft_update', draft); @@ -525,3 +525,25 @@ httpServer.listen(Number(PORT), '0.0.0.0', () => { } } }); + +const gracefulShutdown = () => { + console.log('Received kill signal, shutting down gracefully'); + clearInterval(draftInterval); + + io.close(() => { + console.log('Socket.io closed'); + }); + + httpServer.close(() => { + console.log('Closed out remaining connections'); + process.exit(0); + }); + + setTimeout(() => { + console.error('Could not close connections in time, forcefully shutting down'); + process.exit(1); + }, 10000); +}; + +process.on('SIGTERM', gracefulShutdown); +process.on('SIGINT', gracefulShutdown);