diff --git a/libs/shutdown/README.md b/libs/shutdown/README.md index c995f25..aaefde5 100644 --- a/libs/shutdown/README.md +++ b/libs/shutdown/README.md @@ -1,6 +1,6 @@ # @stock-bot/shutdown -Graceful shutdown management library for Node.js applications in the Stock Bot platform. +Shutdown management library for Node.js applications in the Stock Bot platform. ## Features @@ -9,8 +9,8 @@ Graceful shutdown management library for Node.js applications in the Stock Bot p - ✅ **Multiple Callbacks** - Register multiple cleanup functions - ✅ **Timeout Protection** - Configurable shutdown timeout - ✅ **Error Handling** - Failed callbacks don't block shutdown -- ✅ **Debug Logging** - Optional detailed logging - ✅ **TypeScript Support** - Full type definitions +- ✅ **Zero Dependencies** - Lightweight and efficient ## Installation @@ -85,12 +85,11 @@ await shutdownAndExit('manual', 0); #### Manual Instance Management ```typescript -import { GracefulShutdown } from '@stock-bot/shutdown'; +import { Shutdown } from '@stock-bot/shutdown'; -const shutdown = new GracefulShutdown({ +const shutdown = new Shutdown({ timeout: 20000, - autoRegister: true, - debug: true + autoRegister: true }); shutdown.onShutdown(async () => { @@ -107,7 +106,6 @@ const result = await shutdown.shutdown('manual'); interface ShutdownOptions { timeout?: number; // Timeout in ms (default: 30000) autoRegister?: boolean; // Auto-register signals (default: true) - debug?: boolean; // Enable debug logging (default: false) } ``` @@ -150,9 +148,7 @@ onShutdown(async () => { ### Worker Process ```typescript -import { onShutdown, setShutdownDebug } from '@stock-bot/shutdown'; - -setShutdownDebug(true); // Enable debug logging +import { onShutdown } from '@stock-bot/shutdown'; let isRunning = true; diff --git a/libs/shutdown/src/index.ts b/libs/shutdown/src/index.ts index 30dae33..c0a8898 100644 --- a/libs/shutdown/src/index.ts +++ b/libs/shutdown/src/index.ts @@ -1,25 +1,25 @@ /** - * @stock-bot/shutdown - Graceful shutdown management library + * @stock-bot/shutdown - Shutdown management library * * Main exports for the shutdown library */ // Core shutdown classes and types -export { GracefulShutdown } from './graceful-shutdown.js'; +export { Shutdown } from './shutdown.js'; export type { ShutdownCallback, ShutdownOptions, ShutdownResult } from './types.js'; -import { GracefulShutdown } from './graceful-shutdown.js'; +import { Shutdown } from './shutdown.js'; import type { ShutdownResult } from './types.js'; // Global singleton instance -let globalInstance: GracefulShutdown | null = null; +let globalInstance: Shutdown | null = null; /** * Get the global shutdown instance (creates one if it doesn't exist) */ -function getGlobalInstance(): GracefulShutdown { +function getGlobalInstance(): Shutdown { if (!globalInstance) { - globalInstance = GracefulShutdown.getInstance(); + globalInstance = Shutdown.getInstance(); } return globalInstance; } @@ -42,13 +42,6 @@ export function setShutdownTimeout(timeout: number): void { getGlobalInstance().setTimeout(timeout); } -/** - * Enable or disable debug logging - */ -export function setShutdownDebug(enabled: boolean): void { - getGlobalInstance().setDebug(enabled); -} - /** * Check if shutdown is currently in progress */ @@ -82,5 +75,5 @@ export function shutdownAndExit(signal?: string, exitCode = 0): Promise { */ export function resetShutdown(): void { globalInstance = null; - GracefulShutdown.reset(); + Shutdown.reset(); } diff --git a/libs/shutdown/src/shutdown.ts b/libs/shutdown/src/shutdown.ts new file mode 100644 index 0000000..e16e427 --- /dev/null +++ b/libs/shutdown/src/shutdown.ts @@ -0,0 +1,198 @@ +/** + * Shutdown management for Node.js applications + * + * Features: + * - Automatic signal handling (SIGTERM, SIGINT, etc.) + * - Configurable shutdown timeout + * - Multiple cleanup callbacks with error handling + * - Platform-specific signal support (Windows/Unix) + */ + +import type { ShutdownCallback, ShutdownOptions, ShutdownResult } from './types.js'; + +export class Shutdown { + private static instance: Shutdown | null = null; + private isShuttingDown = false; + private shutdownTimeout = 30000; // 30 seconds default + private callbacks: ShutdownCallback[] = []; + private signalHandlersRegistered = false; + + constructor(options: ShutdownOptions = {}) { + this.shutdownTimeout = options.timeout || 30000; + + if (options.autoRegister !== false) { + this.setupSignalHandlers(); + } + } + + /** + * Get or create singleton instance + */ + static getInstance(options?: ShutdownOptions): Shutdown { + if (!Shutdown.instance) { + Shutdown.instance = new Shutdown(options); + } + return Shutdown.instance; + } + + /** + * Reset singleton instance (mainly for testing) + */ + static reset(): void { + Shutdown.instance = null; + } + + /** + * Register a cleanup callback + */ + onShutdown(callback: ShutdownCallback): void { + if (this.isShuttingDown) { + return; + } + this.callbacks.push(callback); + } + + /** + * Set shutdown timeout in milliseconds + */ + setTimeout(timeout: number): void { + if (timeout <= 0) { + throw new Error('Shutdown timeout must be positive'); + } + this.shutdownTimeout = timeout; + } + + /** + * Get current shutdown state + */ + isShutdownInProgress(): boolean { + return this.isShuttingDown; + } + + /** + * Get number of registered callbacks + */ + getCallbackCount(): number { + return this.callbacks.length; + } + + /** + * Initiate graceful shutdown + */ + async shutdown(signal?: string): Promise { + if (this.isShuttingDown) { + return { + success: false, + callbacksExecuted: 0, + callbacksFailed: 0, + duration: 0, + error: 'Shutdown already in progress' + }; + } + + this.isShuttingDown = true; + const startTime = Date.now(); + + const shutdownPromise = this.executeCallbacks(); + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('Shutdown timeout')), this.shutdownTimeout); + }); + + let result: ShutdownResult; + + try { + const callbackResult = await Promise.race([shutdownPromise, timeoutPromise]); + const duration = Date.now() - startTime; + + result = { + success: true, + callbacksExecuted: callbackResult.executed, + callbacksFailed: callbackResult.failed, + duration, + error: callbackResult.failed > 0 ? `${callbackResult.failed} callbacks failed` : undefined + }; + } catch (error) { + const duration = Date.now() - startTime; + const errorMessage = error instanceof Error ? error.message : String(error); + + result = { + success: false, + callbacksExecuted: 0, + callbacksFailed: 0, + duration, + error: errorMessage + }; + } + + // Don't call process.exit here - let the caller decide + return result; + } + + /** + * Initiate shutdown and exit process + */ + async shutdownAndExit(signal?: string, exitCode = 0): Promise { + const result = await this.shutdown(signal); + const finalExitCode = result.success ? exitCode : 1; + + process.exit(finalExitCode); + } + + /** + * Execute all registered callbacks + */ + private async executeCallbacks(): Promise<{ executed: number; failed: number }> { + if (this.callbacks.length === 0) { + return { executed: 0, failed: 0 }; + } + + const results = await Promise.allSettled( + this.callbacks.map(async (callback) => { + await callback(); + }) + ); + + const failed = results.filter(result => result.status === 'rejected').length; + const executed = results.length; + + return { executed, failed }; + } + + /** + * Setup signal handlers for graceful shutdown + */ + private setupSignalHandlers(): void { + if (this.signalHandlersRegistered) { + return; + } + + // Platform-specific signals + const signals: NodeJS.Signals[] = process.platform === 'win32' + ? ['SIGINT', 'SIGTERM'] + : ['SIGTERM', 'SIGINT', 'SIGUSR2']; + + signals.forEach(signal => { + process.on(signal, () => { + this.shutdownAndExit(signal).catch(() => { + process.exit(1); + }); + }); + }); + + // Handle uncaught exceptions + process.on('uncaughtException', () => { + this.shutdownAndExit('uncaughtException', 1).catch(() => { + process.exit(1); + }); + }); + + // Handle unhandled promise rejections + process.on('unhandledRejection', () => { + this.shutdownAndExit('unhandledRejection', 1).catch(() => { + process.exit(1); + }); + }); + + this.signalHandlersRegistered = true; + } +} diff --git a/libs/shutdown/src/types.ts b/libs/shutdown/src/types.ts index 7cab84b..04e6457 100644 --- a/libs/shutdown/src/types.ts +++ b/libs/shutdown/src/types.ts @@ -1,5 +1,5 @@ /** - * Types for graceful shutdown functionality + * Types for shutdown functionality */ /** @@ -8,15 +8,13 @@ export type ShutdownCallback = () => Promise | void; /** - * Options for configuring graceful shutdown behavior + * Options for configuring shutdown behavior */ export interface ShutdownOptions { /** Timeout in milliseconds before forcing shutdown (default: 30000) */ timeout?: number; /** Whether to automatically register signal handlers (default: true) */ autoRegister?: boolean; - /** Whether to enable debug logging (default: false) */ - debug?: boolean; } /**