@tehreet/conduit
Version:
LLM API gateway with intelligent routing, robust process management, and health monitoring
162 lines • 6.36 kB
JavaScript
;
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