lynkr
Version:
Self-hosted LLM gateway and tier-routing proxy for Claude Code, Cursor, and Codex. Routes across Ollama, AWS Bedrock, OpenRouter, Databricks, Azure OpenAI, llama.cpp, and LM Studio with prompt caching, MCP tools, and 60-80% cost savings.
215 lines (183 loc) • 5.63 kB
JavaScript
const logger = require("../logger");
const { setShuttingDown } = require("../api/health");
const { getBudgetManager } = require("../budget");
/**
* Graceful Shutdown Handler
*
* Performance considerations:
* - Non-blocking shutdown sequence
* - Timeout protection
* - Clean resource cleanup
*/
const DEFAULT_SHUTDOWN_TIMEOUT = 30000; // 30 seconds
class ShutdownManager {
constructor(options = {}) {
this.timeout = options.timeout || DEFAULT_SHUTDOWN_TIMEOUT;
this.isShuttingDown = false;
this.server = null;
this.connections = new Set();
this.shutdownCallbacks = [];
}
/**
* Register a callback to be called during shutdown
*/
onShutdown(callback) {
if (typeof callback === 'function') {
this.shutdownCallbacks.push(callback);
}
}
/**
* Register HTTP server
*/
registerServer(server) {
this.server = server;
// Track all connections
server.on("connection", (conn) => {
this.connections.add(conn);
conn.on("close", () => {
this.connections.delete(conn);
});
});
}
/**
* Setup signal handlers
*/
setupSignalHandlers() {
// Handle SIGTERM (Kubernetes, Docker, etc.)
process.on("SIGTERM", async () => {
logger.info("Received SIGTERM, starting graceful shutdown");
await this.shutdown("SIGTERM");
});
// Handle SIGINT (Ctrl+C)
process.on("SIGINT", async () => {
logger.info("Received SIGINT, starting graceful shutdown");
await this.shutdown("SIGINT");
});
// Handle uncaught exceptions
process.on("uncaughtException", (err) => {
logger.error({ err }, "Uncaught exception, forcing shutdown");
this.forceShutdown("uncaughtException");
});
// Handle unhandled rejections
process.on("unhandledRejection", (reason, promise) => {
logger.error({ reason, promise }, "Unhandled rejection, forcing shutdown");
this.forceShutdown("unhandledRejection");
});
}
/**
* Graceful shutdown sequence
*/
async shutdown(signal) {
if (this.isShuttingDown) {
logger.warn("Shutdown already in progress");
return;
}
this.isShuttingDown = true;
setShuttingDown(true);
const startTime = Date.now();
logger.info({ signal }, "Starting graceful shutdown");
// Set timeout for forced shutdown
const forceTimer = setTimeout(() => {
logger.error("Shutdown timeout exceeded, forcing exit");
this.forceShutdown("timeout");
}, this.timeout);
try {
// Step 1: Stop accepting new connections
logger.info("Step 1: Stopping new connections");
if (this.server) {
await new Promise((resolve) => {
this.server.close(() => {
logger.info("Server stopped accepting new connections");
resolve();
});
});
}
// Step 2: Close idle connections
logger.info(`Step 2: Closing ${this.connections.size} active connections`);
for (const conn of this.connections) {
if (!conn.destroyed) {
conn.destroy();
}
}
// Step 3: Stop background tasks
logger.info("Step 3: Stopping background tasks");
try {
const { getSessionCleanupManager } = require("../sessions/cleanup");
const sessionCleanup = getSessionCleanupManager();
if (sessionCleanup) {
sessionCleanup.stop();
}
} catch (err) {
logger.warn({ err }, "Error stopping session cleanup");
}
// Step 4: Close cache databases
logger.info("Step 4: Closing cache databases");
try {
const promptCache = require("../cache/prompt");
if (promptCache && typeof promptCache.close === 'function') {
promptCache.close();
}
} catch (err) {
logger.warn({ err }, "Error closing prompt cache");
}
// Step 5: Close database connections
logger.info("Step 5: Closing database connections");
try {
const budgetManager = getBudgetManager();
if (budgetManager) {
budgetManager.close();
}
} catch (err) {
logger.warn({ err }, "Error closing budget manager");
}
// Step 6: Destroy HTTP agents
logger.info("Step 6: Destroying HTTP agents");
try {
const databricks = require("../clients/databricks");
if (databricks && typeof databricks.destroyHttpAgents === 'function') {
databricks.destroyHttpAgents();
}
} catch (err) {
logger.warn({ err }, "Error destroying HTTP agents");
}
// Step 7: Final cleanup
logger.info("Step 7: Final cleanup");
clearTimeout(forceTimer);
const duration = Date.now() - startTime;
logger.info({ duration }, "Graceful shutdown completed successfully");
process.exit(0);
} catch (err) {
logger.error({ err }, "Error during graceful shutdown");
clearTimeout(forceTimer);
this.forceShutdown("error");
}
}
/**
* Force shutdown (immediate)
*/
forceShutdown(reason) {
logger.error({ reason }, "Forcing immediate shutdown");
// Try to close budget manager
try {
const budgetManager = getBudgetManager();
if (budgetManager) {
budgetManager.close();
}
} catch (err) {
// Ignore errors during force shutdown
}
process.exit(1);
}
}
// Singleton instance
let instance = null;
function getShutdownManager(options) {
if (!instance) {
instance = new ShutdownManager(options);
}
return instance;
}
module.exports = {
ShutdownManager,
getShutdownManager,
};