UNPKG

@tehreet/conduit

Version:

LLM API gateway with intelligent routing, robust process management, and health monitoring

162 lines 6.36 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GracefulShutdown = void 0; const pidManager_1 = require("./pidManager"); /** * Manages graceful shutdown of the server */ class GracefulShutdown { constructor(options = {}) { this.server = null; this.isShuttingDown = false; this.activeConnections = new Set(); this.shutdownTimeout = options.timeout || 30000; // 30 seconds default this.onShutdown = options.onShutdown; this.setupSignalHandlers(); } /** * Register a server for graceful shutdown */ registerServer(server) { this.server = server; // Track connections server.on('connection', connection => { this.activeConnections.add(connection); connection.on('close', () => { this.activeConnections.delete(connection); }); }); } /** * Setup signal handlers */ setupSignalHandlers() { // Handle graceful shutdown signals const shutdownHandler = async (signal) => { if (this.isShuttingDown) { console.log(`Already shutting down, ignoring ${signal}`); return; } console.log(`\nReceived ${signal}, starting graceful shutdown...`); await this.shutdown(); }; process.on('SIGTERM', () => shutdownHandler('SIGTERM')); process.on('SIGINT', () => shutdownHandler('SIGINT')); // Handle unexpected errors process.on('uncaughtException', async (error) => { console.error('Uncaught Exception:', error); await this.shutdown(1); }); process.on('unhandledRejection', async (reason, promise) => { console.error('Unhandled Rejection at:', promise, 'reason:', reason); await this.shutdown(1); }); } /** * Perform graceful shutdown */ async shutdown(exitCode = 0) { if (this.isShuttingDown) { return; } this.isShuttingDown = true; const startTime = Date.now(); try { // Step 1: Stop accepting new connections if (this.server) { console.log('Stopping server from accepting new connections...'); await new Promise((resolve, reject) => { this.server.close(err => { if (err) { console.error('Error closing server:', err); reject(err); } else { console.log('Server stopped accepting new connections'); resolve(); } }); }); } // Step 2: Close active connections gracefully if (this.activeConnections.size > 0) { console.log(`Waiting for ${this.activeConnections.size} active connections to close...`); // Send Connection: close header to HTTP connections for (const connection of this.activeConnections) { if (connection.setHeader && typeof connection.setHeader === 'function') { try { connection.setHeader('Connection', 'close'); } catch (e) { // Headers may have already been sent } } } // Wait for connections to close with timeout const connectionTimeout = Math.min(10000, this.shutdownTimeout / 3); const connectionClosePromise = new Promise(resolve => { const checkInterval = setInterval(() => { if (this.activeConnections.size === 0) { clearInterval(checkInterval); resolve(); } }, 100); }); const timeoutPromise = new Promise(resolve => { setTimeout(() => { console.log(`Connection timeout reached, ${this.activeConnections.size} connections remaining`); resolve(); }, connectionTimeout); }); await Promise.race([connectionClosePromise, timeoutPromise]); } // Step 3: Run custom shutdown handler if (this.onShutdown) { console.log('Running custom shutdown handler...'); const customTimeout = Math.min(10000, this.shutdownTimeout / 3); await Promise.race([ this.onShutdown(), new Promise(resolve => { setTimeout(() => { console.log('Custom shutdown handler timeout'); resolve(); }, customTimeout); }), ]); } // Step 4: Force close remaining connections if (this.activeConnections.size > 0) { console.log(`Force closing ${this.activeConnections.size} remaining connections...`); for (const connection of this.activeConnections) { try { connection.destroy(); } catch (e) { // Connection might already be closed } } } // Step 5: Clean up PID file console.log('Cleaning up PID file...'); pidManager_1.PidManager.cleanupPidFile(); const duration = Date.now() - startTime; console.log(`Graceful shutdown completed in ${duration}ms`); } catch (error) { console.error('Error during shutdown:', error); } finally { // Exit the process process.exit(exitCode); } } /** * Check if shutdown is in progress */ isShuttingDownNow() { return this.isShuttingDown; } } exports.GracefulShutdown = GracefulShutdown; //# sourceMappingURL=gracefulShutdown.js.map