like-process
Version:
Handle resources and events for gracefully exit
203 lines (164 loc) • 5.03 kB
JavaScript
/*
like-process (https://npmjs.com/package/like-process)
Copyright 2019 Lucas Barrena
Licensed under MIT (https://github.com/LuKks/like-process)
*/
;
const cluster = require('cluster');
const EventEmitter = require('events');
let like = new EventEmitter();
like.terminated = false;
like.cleanup = false;
like.fallback = true;
like.exitCode = {
exception: 1,
rejection: 1
};
like.isMaster = cluster.isMaster;
like.isWorker = cluster.isWorker;
like.isCluster = cluster.isWorker; // is worker or master with at least 1 fork
let servers = [];
let immediateReadyId;
like.fork = function (env) {
if (!cluster.isMaster) return;
like.isCluster = true;
env = Object.assign({
LIKE_PROCESS_FORK: true
}, env);
let worker = cluster.fork(env);
worker.$env = env;
worker.process.on('internalMessage', (msg) => {
if (msg.cmd !== 'NODE_LIKE_PROCESS') return;
if (msg.action === 'exit') like.exit(undefined, worker);
else if (msg.action === 'reload') like.reload(undefined, worker);
});
return worker;
};
like.exit = terminate.bind(null, 'exit');
like.reload = terminate.bind(null, 'reload');
function terminate (action, code, worker) {
worker = typeof worker === 'object' ? worker : undefined;
if ((!like.isCluster || cluster.isWorker) && isFinite(code)) {
process.exitCode = code;
}
if (like.terminated || worker && worker.terminated) {
return;
}
if (!like.isCluster || cluster.isWorker) {
like.terminated = true;
like.emit('terminate');
} else if (worker) {
worker.terminated = true;
like.emit('terminate', worker);
}
if (!like.isCluster) {
exit();
} else if (cluster.isMaster) {
if (worker) {
action === 'exit' ? exit(worker) : reload(worker);
} else {
for (let id in cluster.workers) {
terminate(action, code, cluster.workers[id]);
}
}
} else if (cluster.isWorker) {
if (process.env.LIKE_PROCESS_FORK) {
process.connected && process.send({ cmd: 'NODE_LIKE_PROCESS', action });
} else {
exit(cluster.worker);
}
}
}
function exit (worker) {
if (worker) {
worker.disconnect();
} else {
if (!servers.length) {
// just to emit cleanup
handler.bind(null, () => {}, '')();
}
for (let i = 0; i < servers.length; i++) {
servers[i].close();
}
}
}
function reload (worker) {
if (!worker.$env) return;
if (worker.$env.LIKE_PROCESS_FORK) {
let newer = like.fork(worker.$env);
// wait ready signal from new worker then exit old worker
newer.process.on('internalMessage', function onReady (msg) {
if (msg.cmd === 'NODE_LIKE_PROCESS' && msg.action === 'ready') {
newer.process.removeListener('internalMessage', onReady);
exit(worker);
}
});
}
}
like.handle = function (events, callback) {
if (!Array.isArray(events)) events = [events];
if (!callback) callback = (evt, arg1) => {};
for (let k in events) {
if (typeof events[k] === 'object') {
let server = events[k];
if (servers.indexOf(server) === -1) {
servers.push(server);
if (!server.listening) {
server.once('listening', immediateReady);
}
}
server.once('close', handler.bind(null, callback, 'server', server));
} else {
process.on(events[k], handler.bind(null, callback, events[k]));
}
}
immediateReady();
};
function handler (callback, event, arg1, arg2) {
if (process.exitCode === undefined) {
if (event === 'uncaughtException' && typeof like.exitCode.exception === 'number') {
process.exitCode = like.exitCode.exception;
}
if (event === 'unhandledRejection' && typeof like.exitCode.rejection === 'number') {
process.exitCode = like.exitCode.rejection;
}
}
// server closed
if (event === 'server') {
servers.splice(servers.indexOf(arg1), 1);
}
// wasn't manually, for example, was an uncaught exception
if (!like.terminated) {
!like.isCluster || !like.fallback ? like.exit() : like.reload();
}
// the first event without servers listening or forced exit will turn cleanup
if (!like.cleanup && (!servers.length || event === 'exit')) {
like.cleanup = true;
like.emit('cleanup');
}
callback(event, arg1, arg2);
}
function immediateReady () {
for (let i = 0; i < servers.length; i++) {
if (!servers[i].listening) {
return;
}
}
clearImmediate(immediateReadyId);
immediateReadyId = setImmediate(sendReady);
}
function sendReady () {
if (!cluster.isWorker || !process.connected) return;
if (process.env.LIKE_PROCESS_FORK) {
process.send({ cmd: 'NODE_LIKE_PROCESS', action: 'ready' });
}
if (process.env.PM2_HOME && process.env.wait_ready) {
process.send('ready');
}
}
process.on('SIGTERM', like.exit); // swarm, k8s, systemd, etc
if (process.env.PM2_HOME) {
process.on('SIGINT', like.exit); // pm2 cluster and fork
}
process.on('SIGHUP', like.reload); // native cluster, systemd, etc
module.exports = like;