@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
JavaScript
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