process-cleanup.ts•4.66 kB
/**
* Process Cleanup Utilities
* Handles graceful shutdown and resource cleanup
*/
export interface CleanupHandler {
name: string;
handler: () => Promise<void> | void;
priority: number; // Lower numbers run first
}
export class ProcessCleanup {
private static instance: ProcessCleanup | null = null;
private handlers: CleanupHandler[] = [];
private isShuttingDown = false;
private shutdownTimeout = 10000; // 10 seconds
private signalsRegistered = false;
private constructor() {}
/**
* Get singleton instance
*/
static getInstance(): ProcessCleanup {
if (!ProcessCleanup.instance) {
ProcessCleanup.instance = new ProcessCleanup();
}
return ProcessCleanup.instance;
}
/**
* Register a cleanup handler
*/
registerHandler(handler: CleanupHandler): void {
this.handlers.push(handler);
this.handlers.sort((a, b) => a.priority - b.priority);
// Register signal handlers on first handler registration
if (!this.signalsRegistered) {
this.registerSignalHandlers();
this.signalsRegistered = true;
}
}
/**
* Unregister a cleanup handler by name
*/
unregisterHandler(name: string): void {
this.handlers = this.handlers.filter(h => h.name !== name);
}
/**
* Set shutdown timeout
*/
setShutdownTimeout(timeout: number): void {
this.shutdownTimeout = timeout;
}
/**
* Perform graceful shutdown
*/
async shutdown(reason: string = 'unknown'): Promise<void> {
if (this.isShuttingDown) {
return;
}
this.isShuttingDown = true;
console.error(`Initiating graceful shutdown (reason: ${reason})...`);
// Set a timeout to force exit if cleanup takes too long
const timeoutHandle = setTimeout(() => {
console.error('Shutdown timeout reached, forcing exit');
process.exit(1);
}, this.shutdownTimeout);
try {
// Run all cleanup handlers in priority order
for (const handler of this.handlers) {
try {
console.error(`Running cleanup handler: ${handler.name}`);
await handler.handler();
console.error(`✓ Cleanup handler completed: ${handler.name}`);
} catch (error) {
console.error(`✗ Cleanup handler failed: ${handler.name}`, error);
}
}
clearTimeout(timeoutHandle);
console.error('Graceful shutdown completed');
process.exit(0);
} catch (error) {
clearTimeout(timeoutHandle);
console.error('Error during shutdown:', error);
process.exit(1);
}
}
/**
* Register signal handlers for graceful shutdown
*/
private registerSignalHandlers(): void {
const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGQUIT'];
signals.forEach(signal => {
process.on(signal, () => {
this.shutdown(signal).catch(error => {
console.error('Error during signal-triggered shutdown:', error);
process.exit(1);
});
});
});
// Handle uncaught exceptions
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
this.shutdown('uncaughtException').catch(() => {
process.exit(1);
});
});
// Handle unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
this.shutdown('unhandledRejection').catch(() => {
process.exit(1);
});
});
// Handle process exit
process.on('exit', (code) => {
if (!this.isShuttingDown) {
console.error(`Process exiting with code ${code}`);
}
});
}
/**
* Check if shutdown is in progress
*/
isShutdownInProgress(): boolean {
return this.isShuttingDown;
}
/**
* Get registered handlers count
*/
getHandlerCount(): number {
return this.handlers.length;
}
/**
* Get handler names
*/
getHandlerNames(): string[] {
return this.handlers.map(h => h.name);
}
}
/**
* Convenience function to register a cleanup handler
*/
export function registerCleanupHandler(
name: string,
handler: () => Promise<void> | void,
priority: number = 100
): void {
ProcessCleanup.getInstance().registerHandler({ name, handler, priority });
}
/**
* Convenience function to trigger shutdown
*/
export function triggerShutdown(reason: string = 'manual'): Promise<void> {
return ProcessCleanup.getInstance().shutdown(reason);
}
/**
* Convenience function to check if shutdown is in progress
*/
export function isShutdownInProgress(): boolean {
return ProcessCleanup.getInstance().isShutdownInProgress();
}