feat: Implement graceful server shutdown with signal handling and interval clearing.
All checks were successful
Build and Deploy / build (push) Successful in 1m16s
All checks were successful
Build and Deploy / build (push) Successful in 1m16s
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user