finished shutdown lib
This commit is contained in:
parent
97dcd30223
commit
13c2f10db9
4 changed files with 213 additions and 28 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
# @stock-bot/shutdown
|
# @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
|
## Features
|
||||||
|
|
||||||
|
|
@ -9,8 +9,8 @@ Graceful shutdown management library for Node.js applications in the Stock Bot p
|
||||||
- ✅ **Multiple Callbacks** - Register multiple cleanup functions
|
- ✅ **Multiple Callbacks** - Register multiple cleanup functions
|
||||||
- ✅ **Timeout Protection** - Configurable shutdown timeout
|
- ✅ **Timeout Protection** - Configurable shutdown timeout
|
||||||
- ✅ **Error Handling** - Failed callbacks don't block shutdown
|
- ✅ **Error Handling** - Failed callbacks don't block shutdown
|
||||||
- ✅ **Debug Logging** - Optional detailed logging
|
|
||||||
- ✅ **TypeScript Support** - Full type definitions
|
- ✅ **TypeScript Support** - Full type definitions
|
||||||
|
- ✅ **Zero Dependencies** - Lightweight and efficient
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|
@ -85,12 +85,11 @@ await shutdownAndExit('manual', 0);
|
||||||
#### Manual Instance Management
|
#### Manual Instance Management
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { GracefulShutdown } from '@stock-bot/shutdown';
|
import { Shutdown } from '@stock-bot/shutdown';
|
||||||
|
|
||||||
const shutdown = new GracefulShutdown({
|
const shutdown = new Shutdown({
|
||||||
timeout: 20000,
|
timeout: 20000,
|
||||||
autoRegister: true,
|
autoRegister: true
|
||||||
debug: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
shutdown.onShutdown(async () => {
|
shutdown.onShutdown(async () => {
|
||||||
|
|
@ -107,7 +106,6 @@ const result = await shutdown.shutdown('manual');
|
||||||
interface ShutdownOptions {
|
interface ShutdownOptions {
|
||||||
timeout?: number; // Timeout in ms (default: 30000)
|
timeout?: number; // Timeout in ms (default: 30000)
|
||||||
autoRegister?: boolean; // Auto-register signals (default: true)
|
autoRegister?: boolean; // Auto-register signals (default: true)
|
||||||
debug?: boolean; // Enable debug logging (default: false)
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -150,9 +148,7 @@ onShutdown(async () => {
|
||||||
### Worker Process
|
### Worker Process
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { onShutdown, setShutdownDebug } from '@stock-bot/shutdown';
|
import { onShutdown } from '@stock-bot/shutdown';
|
||||||
|
|
||||||
setShutdownDebug(true); // Enable debug logging
|
|
||||||
|
|
||||||
let isRunning = true;
|
let isRunning = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
/**
|
/**
|
||||||
* @stock-bot/shutdown - Graceful shutdown management library
|
* @stock-bot/shutdown - Shutdown management library
|
||||||
*
|
*
|
||||||
* Main exports for the shutdown library
|
* Main exports for the shutdown library
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Core shutdown classes and types
|
// Core shutdown classes and types
|
||||||
export { GracefulShutdown } from './graceful-shutdown.js';
|
export { Shutdown } from './shutdown.js';
|
||||||
export type { ShutdownCallback, ShutdownOptions, ShutdownResult } from './types.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';
|
import type { ShutdownResult } from './types.js';
|
||||||
|
|
||||||
// Global singleton instance
|
// 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)
|
* Get the global shutdown instance (creates one if it doesn't exist)
|
||||||
*/
|
*/
|
||||||
function getGlobalInstance(): GracefulShutdown {
|
function getGlobalInstance(): Shutdown {
|
||||||
if (!globalInstance) {
|
if (!globalInstance) {
|
||||||
globalInstance = GracefulShutdown.getInstance();
|
globalInstance = Shutdown.getInstance();
|
||||||
}
|
}
|
||||||
return globalInstance;
|
return globalInstance;
|
||||||
}
|
}
|
||||||
|
|
@ -42,13 +42,6 @@ export function setShutdownTimeout(timeout: number): void {
|
||||||
getGlobalInstance().setTimeout(timeout);
|
getGlobalInstance().setTimeout(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable or disable debug logging
|
|
||||||
*/
|
|
||||||
export function setShutdownDebug(enabled: boolean): void {
|
|
||||||
getGlobalInstance().setDebug(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if shutdown is currently in progress
|
* Check if shutdown is currently in progress
|
||||||
*/
|
*/
|
||||||
|
|
@ -82,5 +75,5 @@ export function shutdownAndExit(signal?: string, exitCode = 0): Promise<never> {
|
||||||
*/
|
*/
|
||||||
export function resetShutdown(): void {
|
export function resetShutdown(): void {
|
||||||
globalInstance = null;
|
globalInstance = null;
|
||||||
GracefulShutdown.reset();
|
Shutdown.reset();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
198
libs/shutdown/src/shutdown.ts
Normal file
198
libs/shutdown/src/shutdown.ts
Normal file
|
|
@ -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<ShutdownResult> {
|
||||||
|
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<never>((_, 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<never> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Types for graceful shutdown functionality
|
* Types for shutdown functionality
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -8,15 +8,13 @@
|
||||||
export type ShutdownCallback = () => Promise<void> | void;
|
export type ShutdownCallback = () => Promise<void> | void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for configuring graceful shutdown behavior
|
* Options for configuring shutdown behavior
|
||||||
*/
|
*/
|
||||||
export interface ShutdownOptions {
|
export interface ShutdownOptions {
|
||||||
/** Timeout in milliseconds before forcing shutdown (default: 30000) */
|
/** Timeout in milliseconds before forcing shutdown (default: 30000) */
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
/** Whether to automatically register signal handlers (default: true) */
|
/** Whether to automatically register signal handlers (default: true) */
|
||||||
autoRegister?: boolean;
|
autoRegister?: boolean;
|
||||||
/** Whether to enable debug logging (default: false) */
|
|
||||||
debug?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue