env-cmd/dist/signal-termination.js

117 lines
4.2 KiB
JavaScript

const SIGNALS_TO_HANDLE = [
'SIGINT', 'SIGTERM', 'SIGHUP',
];
export class TermSignals {
terminateSpawnedProcessFuncHandlers = {};
terminateSpawnedProcessFuncExitHandler;
verbose = false;
_exitCalled = false;
constructor(options = {}) {
this.verbose = options.verbose === true;
}
handleTermSignals(proc) {
// Terminate child process if parent process receives termination events
const terminationFunc = (signal) => {
this._removeProcessListeners();
if (!this._exitCalled) {
if (this.verbose) {
console.info('Parent process exited with signal: '
+ signal.toString()
+ '. Terminating child process...');
}
// Mark shared state so we do not run into a signal/exit loop
this._exitCalled = true;
// Use the signal code if it is an error code
// let correctSignal: NodeJS.Signals | undefined
if (typeof signal === 'number') {
if (signal > 0) {
// code = signal
signal = 'SIGINT';
}
}
// else {
// correctSignal = signal
// }
// Kill the child process
proc.kill(signal);
// Terminate the parent process
this._terminateProcess(signal);
}
};
for (const signal of SIGNALS_TO_HANDLE) {
this.terminateSpawnedProcessFuncHandlers[signal] = terminationFunc;
process.once(signal, this.terminateSpawnedProcessFuncHandlers[signal]);
}
this.terminateSpawnedProcessFuncExitHandler = terminationFunc;
process.once('exit', this.terminateSpawnedProcessFuncExitHandler);
// Terminate parent process if child process receives termination events
proc.on('exit', (code, signal) => {
this._removeProcessListeners();
if (!this._exitCalled) {
if (this.verbose) {
console.info(`Child process exited with code: ${(code ?? '').toString()} and signal:`
+ (signal ?? '').toString()
+ '. Terminating parent process...');
}
// Mark shared state so we do not run into a signal/exit loop
this._exitCalled = true;
// Use the signal code if it is an error code
let correctSignal;
if (typeof signal === 'number') {
if (signal > (code ?? 0)) {
code = signal;
correctSignal = 'SIGINT';
}
}
else {
correctSignal = signal ?? undefined;
}
// Terminate the parent process
this._terminateProcess(correctSignal ?? code);
}
});
}
/**
* Enables catching of unhandled exceptions
*/
handleUncaughtExceptions() {
process.on('uncaughtException', (e) => {
this._uncaughtExceptionHandler(e);
});
}
/**
* Terminate parent process helper
*/
_terminateProcess(signal) {
if (signal != null) {
if (typeof signal === 'string') {
process.kill(process.pid, signal);
return;
}
if (typeof signal === 'number') {
process.exit(signal);
return;
}
}
throw new Error('Unable to terminate parent process successfully');
}
/**
* Exit event listener clean up helper
*/
_removeProcessListeners() {
SIGNALS_TO_HANDLE.forEach((signal) => {
process.removeListener(signal, this.terminateSpawnedProcessFuncHandlers[signal]);
});
if (this.terminateSpawnedProcessFuncExitHandler != null) {
process.removeListener('exit', this.terminateSpawnedProcessFuncExitHandler);
}
}
/**
* General exception handler
*/
_uncaughtExceptionHandler(e) {
console.error(e.message);
process.exit(1);
}
}