nodejs shutdown hook on killall - node.js

I'm trying to bind some shutdown function to my nodejs application (version 0.8.12). Since I'm spawning A LOT of child processes and working in a distributed evironment, I'm currently killing the application through
var n = spawn('killall', ['node']);
The problem is that with this apparently the on('exit', ...) logic is no longer working, indeed I have something like this:
process.on('exit', function() {
if(exit_cb)
exit_cb()
else
console.log('About to exit.');
});
And it's not firing whenever I kill the application.
Is there a way to add a shutdown hook with a killall command or should I find another way to kill my child processes in order to have the hook working?
Thanks

You need to listen for SIGTERM signal that killall sends by dafault. But also you need to stop your process manually after all jobs was finished.
process.on('SIGTERM', function() {
if(exit_cb) {
exit_cb();
process.exit();
} else {
console.log('About to exit.');
process.exit();
}
});

Related

Descendent of a child process not receiving SIGTERM on ubuntu but receives on mac

So I have some code which runs a command in a spawned child process. I do this using the execa module.
const childProcess = execa.command('yarn start');
const localhostStarted = await waitForLocalhost({ port: 8000 });
expect(localhostStarted.done).toBe(true);
childProcess.kill('SIGINT', { forceKillAfterTimeout: 2000 });
The yarn start command executes webpack-dev-server in another child process of its own. However when I kill the childProcess that I spawned, it does not automatically kill its spawned webpack-dev-server process. It is known to be an issue here https://github.com/webpack/webpack-dev-server/issues/2168.
To fix this I add manual listeners for SIGINT & SIGTERM inside my script which runs when yarn start is called
['SIGINT', 'SIGTERM'].forEach((signal) => {
console.log('registering events');
process.on(signal, () => {
console.log('received signal', signal);
devServer.close(() => {
console.log('exiting proces');
process.exit(0);
});
});
});
This fixes the issue on my local machine and when I kill child process I spawn, it kills all its descendents i.e the dev-server process too.
However, this fix still does not work on CI, and since the child process gets killed on CI but not the dev-server process, my tests dont exit and keeps hanging.
My local machine is OSX 10.15 but on CI we use ubuntu. If I change CI to use macos 10.15, then the fix works on CI too.
I am unable to find any docs on this issue which explains the different behaviour on why the signal of SIGTERM is not received by the dev-server process on ubuntu machines but receives fine on mac machines.

Node.js getting SIGINT from pm2

I'm trying to use pm2 to run my node app as a service.
Right now, starting and stopping the app works. However, I want to do a graceful shutdown.
My app already listens for SIGINT, shutdowns the server and then exits the process. However, trying to put pm2 to send the SIGINT, just causes the app to restart, like if pm2 was killing and starting it again.
This is how I create the process:
pm2 start server.js --name ProcessName --silent --kill-timeout 3000
Here's my app's code for listening the SIGINT:
process.on("SIGINT", function () {
//graceful shutdown
server.end().then(() => {
process.exit();
}).catch((err) => {
console.error(err);
});
});
Then to shutdown the app using pm2, I'm running:
pm2 sendSignal SIGINT ProcessName
Which, again, restarts the app.
Reading over pm2 docs, I found that pm2 will also send a shutdown event to the app, so I added:
process.on('message', function(msg) {
if (msg == 'shutdown') {
server.end().then(() => {
process.exit();
}).catch((err) => {
console.error(err);
});
}
});
Which isn't working either.
Any idea how to solve this?
Thanks!
If you haven't solved it yet...
Based on the information you provided, I assume you are running it on Windows.
Your app cannot catch SIGINT sent by PM2 on Windows.
shutdown message works on windows too, but it is sent only by gracefulReload command.
(update)
These are not complete solutions, but might be helpful (hopefully...)
sendSignal command calls process.kill() eventually, and some of these signals might work (haven't tried).
I also found the below method. This can gracefully shutdown a process without restarting only if autorestart option is turned off.
And then your clusters will not reload in case of an accident, so it might not be what you want though...
pm2 lets you send a custom message (reference).
Put the code below in a new file:
var pm2 = require('pm2');
var id = process.argv[2];
pm2.connect(() => {
pm2.sendDataToProcessId({
type: 'shutdown',
data:{some: 'data'},
id: id,
topic: 'some topic'
}, (err, res) => {
console.log('message sent');
pm2.disconnect();
if(err) throw err;
})
});
Modify the part that listens the shutdown message like below:
process.on('message', function(msg){
if(msg == 'shutdown' || msg.type == 'shutdown'){
// code to clean up
}
});
And run the first file with node with id of the cluster you want to shutdown as an argument.
The reason for extra msg.type == 'shutdown' in the condition is that pm2.sendDataToProcessId() requires the argument to be an object with those keys, and does not accept simple shutdown string.
In general pm2 stop is the right way to stop your application. However if you run appliation inside of the Docker you need to use pm2-runtime instead of pm2 which is a part of pm2 npm package and passes system SIGINT to all child processes. See http://pm2.keymetrics.io/docs/usage/docker-pm2-nodejs
Catching the sigint and exiting gracefully should work in your first example.
To actually stop the server, use pm2 stop instead of pm2 sendSignal SIGINT ProcessName.
See http://pm2.keymetrics.io/docs/usage/signals-clean-restart/

How to stop pm2 from killing detached child processes

pm2 is killing detached child processes on watch restart (ie which have been spawned with detached:true, stdio:'ignore', and child.unref().
Is there a way of telling pm2 not to kill the tree of child processes on restart?
The answer was to put the following in the ecosystem file (main section for app, not under the watch settings):
"treekill": false
I ended up using
pm2 restart myapp --no-treekill
and
pm2 stop myapp --no-treekill
I restart my nodejs app from within using the pm2 API. Here is the function I use so that detached child processes are not killed (notice the treekill: false option).
function restartPm2() {
pm2.connect(function(err) {
if (err) {
console.error("PM2 FAILED TO CONNECT:", err);
} else {
pm2.restart('myapp.js', { treekill: false }, function() {});
}
});
pm2.disconnect();
}

How to REALLY kill a child_process nodejs

I'm using mocha with Nodejs to test my restApi.
When I run mocha, I tell my test to create a child_process and run the API so I can make requests to it.
The problem is whenever the test exits (finishing or crashing), it seems that the API keeps running on background. I've seen some answers here that instructs to manually kill the child process whenever the main process exits. So I did it like this:
export function startProcess(done) {
const child = spawn('babel-node', ["app.js"]);
child.stdout.on("data", function(data) {
data = data.toString();
// console.log(data.toString());
if(data.indexOf("Server online") > -1) done();
});
child.stderr.on('data', function(err) {
console.log("ERROR: ", err.toString());
});
child.on('exit', function(code) {
console.log("PROPERLY EXITING");
console.log("Child process exited with code", code);
});
process.on('exit', function(code) {
console.log("Killing child process");
child.kill();
console.log("Main process exited with code", code);
});
}
When the main process exits it does log "Killing child process", meaning that child.kill() was indeed called. But if I try to run my test again, when the spawn command gets called, the API throws an error
Error: listen EADDRINUSE :::3300
, meaning that the API is still running and that port address is taken.
So I have to run sudo pkill node to really kill all node process and then npm test works again.
Am I missing something? Is this really the way to achieve what I'm expecting?
I thought about using child_process.exec to run sudo pkill node on my process.on('exit') listener, but that doesnt seem like a smart thing to do.
This is happening both in Mac and Ubuntu.
Any suggestions?
Thanks
"exit" is an event that gets triggered when node finishes it's event loop internally, it's not triggered when you terminate the process externally.
What you're looking for is executing something on a SIGINT.
Have a look at http://nodejs.org/api/process.html#process_signal_events

NodeJS: Can't kill spawn of gulp process

I'd like to restart gulp on certain changes. That can be easily done by placing the following within the gulpfile
spawn('gulp', [], { stdio: 'inherit'});
However, once gulp restarts in this way, the process is no longer killed properly with Ctrl+C via the terminal. If I start gulp via terminal, I can capture a Ctrl+C signal, but can't if gulp was started via the spawn in gulpfile. How can I capture 'SIGINT' for the spawn?
Okay here's the full story to anyone who might encounter the issue. From what I have been reading, whenever you want to restart gulp from within gulp you simply use:
spawn('gulp', [], { stdio: 'inherit'});
process.exit();
I didn't mention process.exit() in my question as I didn't expect it to affect the usage of Ctrl+C. Indeed it was, as my server was an ExpressJS one, whenever I'd use Ctrl+C after gulp restarted from within itself, I would get the port still in use error (Error: listen EADDRINUSE). Obviously, all node processes wasn't being closed. Once I removed the line process.exit() from my code, I was able to use Ctrl+C and successfully close all processes. Below is the useful bit of code in the gulpfile and output in terminal that is related to this issue.
// gulpfile.js
gulp.task('restart', function() {
server.close();
spawn('gulp', [], { stdio: 'inherit'});
process.exit(); // this line needs to be removed
});
process.on('SIGINT', function() {
setTimeout(function() {
gutil.log(gutil.colors.red('Successfully closed ' + process.pid));
process.exit(1);
}, 500);
});
// Console results:
^C[20:12:12] Successfully closed 67160
[20:12:12] Successfully closed 67151

Resources