UNPKG

@atomist/automation-client

Version:

Atomist API for software low-level client

107 lines (96 loc) 3.59 kB
import * as exitHook from "async-exit-hook"; import { get as _get } from "lodash"; import { Configuration } from "../../configuration"; import { logger } from "../../util/logger"; /** Believe or not, this is the default grace period. */ export const defaultGracePeriod = 10000; /** * Return whether graceful termination is enabled. */ export function terminationGraceful(cfg: Configuration): boolean { return _get(cfg, "ws.termination.graceful", false); } /** * Return graceful termination period in milliseconds. */ export function terminationGracePeriod(cfg: Configuration): number { return _get(cfg, "ws.termination.gracePeriod", defaultGracePeriod); } /** * Shutdown hook function and metadata. */ export interface ShutdownHook { /** Function to call at shutdown. */ hook: () => Promise<number>; /** * Priority of hook. Lower number values are executed first. The * number provided should be greater than 0 and less 100000. * Using a priority outside (0, 100000) may interfere with * internal shutdown behaviors. */ priority: number; /** Optional description used in logging. */ description?: string; } let shutdownHooks: ShutdownHook[] = []; /** * Add callback to run when shutdown is initiated prior to process * exit. See [[ShutdownHook]] for description of parameters. */ export function registerShutdownHook(cb: () => Promise<number>, priority: number = 1000, desc?: string): void { const description = desc || `Shutdown hook with priority ${priority}`; shutdownHooks = [{ priority, hook: cb, description }, ...shutdownHooks].sort((h1, h2) => h1.priority - h2.priority); } /** * Run each shutdown hook and collect its result. */ export async function executeShutdownHooks(cb: () => never): Promise<never> { if (shutdownHooks.length === 0) { // logger.info("Shutting down"); cb(); throw new Error(`async-exit-hook callback returned but should not have`); } logger.info("Shutdown initiated, calling shutdown hooks"); let status = 0; for (const hook of shutdownHooks) { try { logger.debug(`Calling shutdown hook '${hook.description}'...`); const result = await hook.hook(); logger.debug(`Shutdown hook '${hook.description}' completed with status '${result}'`); status += result; } catch (e) { logger.warn(`Shutdown hook '${hook.description}' threw an error: ${e.message}`); status += 10; } } logger.info(`Shutdown hooks completed with status '${status}', exiting`); shutdownHooks = []; cb(); throw new Error(`async-exit-hook callback returned but should not have`); } exitHook(executeShutdownHooks); /** * Set the absolute longest number of milliseconds shutdown should * take. */ export function setForceExitTimeout(ms: number): void { exitHook.forceExitTimeout(ms); } /** * Register a final shutdown hook that calls `process.exit(code)` and * then initiates shutdown. This allows you to exit with a specific * exit code _and_ process all async shutdown hooks, something not * possible when calling process.exit directly. * * For the fastest safe exit, set the automation client configuration * ws.termination.graceful to false before calling this. * * @param code Exit code */ export function safeExit(code: number): void { registerShutdownHook(async () => { process.exit(code); return 0; // make the compiler happy }, Number.MAX_VALUE, `safeExit ${code}`); process.kill(process.pid); }