UNPKG

@piggly/fastify-chassis

Version:

An ESM/CommonJS toolkit to help you to do common operations in your back-end applications with Fastify and NodeJS.

191 lines 6.14 kB
import { ServiceProvider, LoggerService } from '@piggly/ddd-toolkit'; import EventBus from '@piggly/event-bus'; import debug from 'debug'; /** * @file Handle graceful shutdown of the service. * @copyright Piggly Lab 2025 * @since 7.5.0 * @author Caique Araujo <caique@piggly.com.br> */ export class GracefulShutdownService { /** * The cleanup service to use. * @since 7.5.0 * @author Caique Araujo <caique@piggly.com.br> */ cleanup; /** * Whether the service is shutting down. * @since 7.5.0 * @author Caique Araujo <caique@piggly.com.br> */ is_shutting_down = false; /** * The timeout in milliseconds. * @since 7.5.0 * @author Caique Araujo <caique@piggly.com.br> */ timeout; /** * Constructor. * @param {CleanUpService} cleanup The cleanup service to use. * @param {number} timeout The timeout in milliseconds. Default is 30000. * @since 7.5.0 * @author Caique Araujo <caique@piggly.com.br> */ constructor(cleanup, timeout = 30000) { this.timeout = timeout; this.cleanup = cleanup; } /** * Get the cleanup service. * * @returns {CleanUpService} * @throws {Error} If cleanup service is not provided. * @since 7.5.0 * @author Caique Araujo <caique@piggly.com.br> */ get handlers() { if (!this.cleanup) { throw new Error('Cleanup service must be provided'); } return this.cleanup; } /** * Fetch the cleanup service. * Useful when you want to delay the cleanup to a later time. * * E.g: You want to listen for shutdown signals and it can * happen before the cleanup service is ready. * * If "cleanup" is not provided, it will try to load the cleanup service * from the ServiceProvider. * * @param {CleanUpService} cleanup The cleanup service to use. * @since 7.5.0 * @author Caique Araujo <caique@piggly.com.br> */ fetch(cleanup) { this.cleanup = cleanup ?? ServiceProvider.get('CleanUpService'); debug('app:shutdown')(`Fetched cleanup service: ${this.cleanup === undefined ? 'NO' : 'YES'}`); } /** * Listen for shutdown signals. * * @since 7.5.0 * @author Caique Araujo <caique@piggly.com.br> */ listen() { process.on('SIGINT', () => this._shutdown('SIGINT')); process.on('SIGTERM', () => this._shutdown('SIGTERM')); process.on('SIGHUP', () => this._shutdown('SIGHUP')); debug('app:shutdown')('Listening for shutdown signals...'); } /** * Listen for uncaught exceptions and rejections. * * @since 7.5.0 * @author Caique Araujo <caique@piggly.com.br> */ listenUncaught() { const handler = async (reason, origin) => { debug('app:uncaught')(reason, origin); debug('app:shutdown:uncaught')('Uncaught exception, shutting down...'); await EventBus.instance.cleanup(); const logger = LoggerService.softResolve(); logger.error('UNCAUGHT_EXCEPTION_ERROR', reason, origin); logger.flush(); await logger.cleanup(); await this._shutdown('UNCAUGHT_EXCEPTION'); }; process.on('uncaughtException', handler); process.on('unhandledRejection', handler); } /** * Listen for user signals. * * @since 7.5.0 * @author Caique Araujo <caique@piggly.com.br> */ listenUSR() { process.on('SIGUSR1', () => this._shutdown('SIGUSR1')); process.on('SIGUSR2', () => this._shutdown('SIGUSR2')); } /** * Shutdown the service. * * @returns {Promise<void>} * @since 7.5.0 * @author Caique Araujo <caique@piggly.com.br> */ async shutdown() { await this._shutdown('MANUAL'); } /** * Clean up the service. * * @returns {Promise<boolean>} * @since 7.5.0 * @author Caique Araujo <caique@piggly.com.br> */ async _clean() { try { LoggerService.softResolve().flush(); await Promise.all([ EventBus.instance.cleanup(), LoggerService.softResolve().cleanup(), ]); if (this.cleanup) { const response = await this.cleanup.softClean(); return response.success; } return true; } catch (error) { debug('app:shutdown:error')(error); return false; } } /** * Shutdown the service. * * @param {string} signal The signal that triggered the shutdown. * @returns {Promise<void>} * @since 7.5.0 * @author Caique Araujo <caique@piggly.com.br> */ async _shutdown(signal) { if (this.is_shutting_down) { debug('app:shutdown')('Already shutting down, skipping...'); return; } this.is_shutting_down = true; debug('app:shutdown')(`Received ${signal} signal, shutting down...`); const timeout = setTimeout(() => { debug('app:shutdown')('Graceful shutdown timed out, forcing exit...'); process.exit(1); }, this.timeout); const response = await this._clean(); clearTimeout(timeout); if (!response) { debug('app:shutdown')('Failed to clean up, forcing exit...'); process.exit(1); } debug('app:shutdown')('Shutdown completed successfully'); process.exit(0); } /** * Resolve the graceful shutdown service. * It will load the CleanUpService from the ServiceProvider. * * @param {number} timeout The timeout in milliseconds. Default is 30000. * @returns {GracefulShutdownService} * @since 7.5.0 * @author Caique Araujo <caique@piggly.com.br> */ static resolve(timeout = 30000) { const service = new GracefulShutdownService(ServiceProvider.get('CleanUpService'), timeout); return service; } } //# sourceMappingURL=GracefulShutdownService.js.map