/** * Graceful Shutdown Handler * * Handles server shutdown gracefully by: * - Closing server to stop accepting new connections * - Waiting for existing connections to finish * - Cleaning up resources * - Exiting with appropriate code */ import logger from './logger.js'; /** * Setup graceful shutdown handlers for Express server * @param {Object} server - HTTP server instance * @param {Object} options - Configuration options */ export function setupGracefulShutdown(server, options = {}) { const { timeout = 30000, // 30 seconds onShutdown = null // Optional cleanup function } = options; let isShuttingDown = false; const connections = new Set(); // Track all connections server.on('connection', (connection) => { connections.add(connection); connection.on('close', () => { connections.delete(connection); }); }); /** * Perform graceful shutdown * @param {string} signal - Signal that triggered shutdown */ async function shutdown(signal) { if (isShuttingDown) { logger.warn('Shutdown already in progress, forcing exit'); process.exit(1); return; } isShuttingDown = true; logger.info(`Received ${signal}, starting graceful shutdown...`); // Stop accepting new connections server.close(() => { logger.info('Server closed, all requests completed'); }); // Set shutdown timeout const shutdownTimeout = setTimeout(() => { logger.error(`Shutdown timeout (${timeout}ms), forcing exit`); connections.forEach((conn) => conn.destroy()); process.exit(1); }, timeout); try { // Run custom cleanup if provided if (onShutdown) { logger.info('Running custom shutdown cleanup...'); await onShutdown(); } // Wait for all connections to close logger.info(`Waiting for ${connections.size} connections to close...`); // Close all idle connections connections.forEach((conn) => { if (!conn.destroyed) { conn.end(); } }); // Give connections time to finish await new Promise((resolve) => { const checkInterval = setInterval(() => { if (connections.size === 0) { clearInterval(checkInterval); resolve(); } }, 100); }); clearTimeout(shutdownTimeout); logger.info('Graceful shutdown complete'); process.exit(0); } catch (error) { logger.error('Error during shutdown', { error: error.message }); clearTimeout(shutdownTimeout); process.exit(1); } } // Register shutdown handlers for various signals const signals = ['SIGTERM', 'SIGINT', 'SIGUSR2']; signals.forEach((signal) => { process.on(signal, () => shutdown(signal)); }); logger.info('Graceful shutdown handlers registered'); } /** * Health check middleware that returns 503 during shutdown */ export function createHealthCheckMiddleware() { let isHealthy = true; process.on('SIGTERM', () => { isHealthy = false; }); process.on('SIGINT', () => { isHealthy = false; }); return (req, res) => { if (!isHealthy) { return res.status(503).json({ status: 'shutting_down', message: 'Server is shutting down' }); } res.json({ status: 'ok', uptime: process.uptime(), timestamp: new Date().toISOString(), memory: process.memoryUsage(), environment: process.env.NODE_ENV }); }; }