137 lines
3.4 KiB
JavaScript
137 lines
3.4 KiB
JavaScript
/**
|
|
* 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
|
|
});
|
|
};
|
|
}
|