Killing a bash script does not kill child processes
Asked Answered
O

3

12

I have written a test script which runs another script to start the server to test. When the tests have completed a SIGKILL message is sent to the server process, however when running the test script again the server throws a EADDRINUSE error (I‘m in a node.js environment) which means the port the server is trying to mount to is currently in use. The process we tried to kill with a SIGKILL is still running. I don‘t believe this is a node specific issue, but rather a lack of education on my end for how bash processes work.

Here are some specifics, this is my start script called scripts/start-node.sh:

#!/bin/bash

node_modules/.bin/babel-node --stage 0 index.js

This is my node server called index.js (I haven‘t authored any process event listeners):

Http.createServer(…).listen(PORT, () => console.log(`Server listening on ${PORT}`))

And the start script is controlled with the node child_process module:

var child = child_process.spawn('scripts/start-node.sh')
// Later…
child.kill('SIGKILL')
Ot answered 25/10, 2015 at 1:45 Comment(6)
Have you tried child.kill('SIGINT') maybe node doesn't support the SIGKILL signal, and will then assume default to: SIGTERM : nodejs.org/api/…Stringfellow
I‘ve tried SIGINT, SIGKILL, SIGHUP, SIGQUIT, and SIGTERM. From what I‘ve read, SIGKILL seems like the most semantic way to do it.Ot
wouldn't it be better to close the server regularly with .close() instead of killing the process?Ryon
I don't see .close() documented hereOt
no, i mean close the server from inside your server code nodejs.org/api/net.html#net_server_close_callbackRyon
So where is that method called?Ot
G
15

To kill a child process and all it's children you may use process.kill with a negative pid (to kill a process group)

var child = child_process.spawn('scripts/start-node.sh', {detached: true})
// Later…
process.kill(-child.pid, 'SIGKILL');

See details on child_process documentation for options.detached

On non-Windows, if the detached option is set, the child process will be made the leader of a new process group and session.

Referencing here a portion of man 2 kill for some details:

If pid is less than -1, then sig is sent to every process in the process group whose ID is -pid.


Another option may be using trap in your shell script to intercept a signal and kill all the children and using child.kill('SIGTERM') from node (as SIGKILL will not be intercepted by trap)

#!/bin/bash

trap 'kill $(jobs -p)' EXIT
node_modules/.bin/babel-node --stage 0 index.js
Grimm answered 27/10, 2015 at 12:12 Comment(2)
Ok, that worked, thank you. Although I would prefer a bash level solution as the same error occurs when using external software like pm2. Should software like pm2 be using the negative pid? If so I can see if I can get a pull request in.Ot
@Calebmer, I've added alternative that you may use on a shell script level, you may also want to read first answer of How to propagate a signal through an arborescence of scripts ? BashGrimm
G
1

Simply:

#!/bin/bash
if pgrep -x "node" > /dev/null
then
mv -f  /usr/local/bin/node /usr/local/bin/node.1
killall  node
mv -f /usr/local/bin/node.1 /usr/local/bin/node
which node
else
echo "process node not exists"
fi

node is creating child process every-time we kill it. So it's not possible to kill the process from kill,pkill or killall commands. So we are removing node command to make forking process fail and then we kill the process.Finally we restore the node command.

Gaptoothed answered 7/2, 2019 at 10:58 Comment(0)
R
0

have a handler for process signals in your server

server.js

 var http = require('http');

function handleRequest(request, response){
    response.end("Hello");
}
var server = http.createServer(handleRequest);

server.listen(3000, function(){console.log("listening...")});
process.title="testserver"

process.on('SIGQUIT', function() {
  server.close()
});

startup.sh

 #!/bin/bash
 node server.js & echo "started"

and then have a "shutdown.sh".

shutdown.sh

#!/bin/bash
pkill -QUIT testserver & echo "stoped"

It will kill all processes with that name, in case you are spawning multible processes in your "start-node.sh". This way you can have some clean-up code when shutting down e.g. closing all connections etc.

and in your test runner you can do

var exec = require('child_process').exec;
exec("./start.sh")
// later...
exec("./stop.sh")
Ryon answered 27/10, 2015 at 12:9 Comment(8)
The problem with this approach is that I want to use a bash script so that it’s interoperable. It's harder in development to continually call start/stop scripts, and I use a process manager which I don't believe has a shutdown script option.Ot
Then why don't you just require('pm2') and start and stop the server from there?Ryon
and btw you cant kill the server when it's managed by pm2, because pm2 will allways restart the server for you when its killed. in the end thats what pm2 is for ;-)Ryon
Because require('pm2') means I lose some of the interoperability which I desire from the bash script. Also, to restart the process with pm2, it first has to stop the process then start it again.Ot
its unclear to me if you use pm2 in your test runner or not. if so, you can not kill the process from outside of pm2! If not, where is the problem with the solution i gave above? " It's harder in development to continually call start/stop scripts" you don't have to call the start an stop scripts, this is only done by your testrunnerRyon
I don't use pm2 in my test runner, only in production. But I would like to use the same bash script for both pm2 and my tests. So if I were to use a pair of start/stop scripts I'd like to use them everywhere. Also, I just wanted to say the process.title is super cool :)Ot
any reason not to use pm2 in your test runner?Ryon
It over complicates things. It's a chainsaw tool when I only need a screwdriver. Also it's currently experiencing the same problem.Ot

© 2022 - 2024 — McMap. All rights reserved.