Skip to content

Instantly share code, notes, and snippets.

@sompylasar
Last active December 19, 2018 05:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sompylasar/53fd31b1be6479ff62878d8aa76ba82d to your computer and use it in GitHub Desktop.
Save sompylasar/53fd31b1be6479ff62878d8aa76ba82d to your computer and use it in GitHub Desktop.
Node.js process graceful interruption from SIGINT. Force termination after receiving certain number of SIGINTs.
const EventEmitter = require('events');
function gracefulSigint({ forceAttempts } = {}) {
let state = {
isEnabled: false,
isTerminating: false,
isTerminatingForce: forceAttempts >= 0 ? forceAttempts : 5,
};
let emitter = new EventEmitter();
let sigintHandler = () => {
if (state.isTerminating) {
if (state.isTerminatingForce > 1) {
--state.isTerminatingForce;
process.stderr.write('\nGot SIGINT, hit it ' + state.isTerminatingForce + ' more times to force terminate.\n');
} else {
emitter.emit('terminated');
process.stderr.write('\nGot SIGINT, force terminated.\n');
process.exit(-1); // eslint-disable-line no-process-exit
}
} else {
process.stderr.write('\nGot SIGINT, will terminate gracefully. Please wait.\n');
state.isTerminating = true;
emitter.emit('terminating');
}
};
let exitHandler = (code) => {
process.stderr.write('\nAbout to exit with code: ' + code + '\n');
};
return {
setEnabled: (isEnabled) => {
if (!state) {
return;
}
if (isEnabled === true || isEnabled === undefined) {
state.isEnabled = true;
process.on('SIGINT', sigintHandler);
process.on('exit', exitHandler);
} else {
state.isEnabled = false;
process.removeListener('SIGINT', sigintHandler);
process.removeListener('exit', exitHandler);
}
},
shouldTerminate: () => (state ? state.isTerminating : false),
addListener: (eventName, callback) => {
if (emitter) {
emitter.addListener(eventName, callback);
}
},
removeListener: (eventName, callback) => {
if (emitter) {
emitter.removeListener(eventName, callback);
}
},
destroy: () => {
if (!state) {
return;
}
state = null;
emitter.removeAllListeners();
emitter = null;
process.removeListener('SIGINT', sigintHandler);
sigintHandler = null;
process.removeListener('exit', exitHandler);
exitHandler = null;
},
};
}
module.exports = {
gracefulSigint,
};

Overview

Node.js process graceful interruption from SIGINT. Interrupt the long async job only at certain safe interruption points. Force termination after receiving certain number of SIGINTs.

Usage

const gracefulSigint = require('./nodeGracefulSigint').gracefulSigint();
const doTaskAsync = require('./doTaskAsync');

async function doManyTasksAsync() {
  const manyTaskInputs = new Array(1000);
  for (const taskInput of manyTaskInputs) {
    // Safe to terminate between the tasks.
    if (gracefulSigint.shouldTerminate()) {
      process.exit(-1);
      return;
    }
    // Unsafe to terminate within the task.
    await doTaskAsync(taskInput);
  }
}

// Run the job only if the module is executed directly, not `require()`d.
if (!module.parent) {
  // Enable graceful interruption.
  gracefulSigint.setEnabled(true);
  doManyTasksAsync().catch((error) => {
    console.error(error);
  });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment