UNPKG

@nanocollective/nanocoder

Version:

A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter

100 lines 3.67 kB
import { loggerProvider } from '../../utils/logging/logger-provider.js'; const DEFAULT_TIMEOUT_MS = 5000; const ENV_SHUTDOWN_TIMEOUT = 'NANOCODER_DEFAULT_SHUTDOWN_TIMEOUT'; export class ShutdownManager { handlers = new Map(); isShuttingDown = false; timeoutMs; boundSigterm; boundSigint; boundUncaughtException; boundUnhandledRejection; constructor(options) { const envTimeout = process.env[ENV_SHUTDOWN_TIMEOUT]; const parsedEnvTimeout = envTimeout ? parseInt(envTimeout, 10) : undefined; const isValidTimeout = Number.isFinite(parsedEnvTimeout); this.timeoutMs = options?.timeoutMs ?? (isValidTimeout ? parsedEnvTimeout : undefined) ?? DEFAULT_TIMEOUT_MS; this.boundSigterm = () => { void this.gracefulShutdown(0); }; this.boundSigint = () => { void this.gracefulShutdown(0); }; this.boundUncaughtException = (err) => { const logger = loggerProvider.getLogger(); logger.fatal({ err }, 'Uncaught exception'); void this.gracefulShutdown(1); }; this.boundUnhandledRejection = (reason, promise) => { const logger = loggerProvider.getLogger(); logger.fatal({ reason, promise }, 'Unhandled promise rejection'); void this.gracefulShutdown(1); }; this.setupSignalHandlers(); } register(handler) { this.handlers.set(handler.name, handler); } unregister(name) { this.handlers.delete(name); } async gracefulShutdown(exitCode = 0) { if (this.isShuttingDown) { return; } this.isShuttingDown = true; const logger = loggerProvider.getLogger(); logger.info('Graceful shutdown initiated', { exitCode }); const sorted = Array.from(this.handlers.values()).sort((a, b) => a.priority - b.priority); const shutdownPromise = (async () => { for (const entry of sorted) { try { logger.info(`Shutting down: ${entry.name}`); await entry.handler(); } catch (err) { logger.error({ err }, `Shutdown handler failed: ${entry.name}`); } } })(); const timeoutPromise = new Promise(resolve => { setTimeout(() => { logger.warn('Shutdown timeout reached, forcing exit'); resolve(); }, this.timeoutMs); }); await Promise.race([shutdownPromise, timeoutPromise]); process.exit(exitCode); } setupSignalHandlers() { process.once('SIGTERM', this.boundSigterm); process.once('SIGINT', this.boundSigint); process.on('uncaughtException', this.boundUncaughtException); process.on('unhandledRejection', this.boundUnhandledRejection); } reset() { this.handlers.clear(); this.isShuttingDown = false; process.removeListener('SIGTERM', this.boundSigterm); process.removeListener('SIGINT', this.boundSigint); process.removeListener('uncaughtException', this.boundUncaughtException); process.removeListener('unhandledRejection', this.boundUnhandledRejection); } } let instance = null; export function getShutdownManager(options) { if (!instance) { instance = new ShutdownManager(options); } return instance; } export function resetShutdownManager() { if (instance) { instance.reset(); instance = null; } } //# sourceMappingURL=shutdown-manager.js.map