DrakaSAN
DrakaSAN

Reputation: 7853

Spawn and kill a process in node.js

I'm trying to spawn a process in javascript, and kill it after some time (for testing purposes).

In the end, the process will be a infinite loop that I need to restart with different arguments at specified time, so I thought that spawning the process and killing it was the best way to do this.

My test code is:

var spawn=require('child_process').spawn
, child=null;

child=spawn('omxplayer', ['test.mp4'], function(){console.log('end');}, {timeout:6000});
console.log('Timeout');
setTimeout(function(){
    console.log('kill');
    child.kill();
}, 1200);

child.stdout.on('data', function(data){
    console.log('stdout:'+data);
});

child.stderr.on('data', function(data){
    console.log('stderr:'+data);
});

child.stdin.on('data', function(data){
    console.log('stdin:'+data);
});

The result is:

#~$ node test.js
Timeout
kill

But I still need to send ctrl+C to end the program. What am I missing?

On Raspbian, node 0.10.17, omxplayer is a binary (video player).

I tried:

I also launched a ps command while the app was running:

2145    bash
2174    node
2175    omxplayer
2176    omxplayer.bin
2177    ps

So omxplayer is a wrapper, who don t kill it's child process when it end, is there any way to get the pid of the wrapped process?

Still biting dust, tried this:

spawn('kill', ['-QUIT', '-$(ps opgid= '+child.pid+')']);

Which I thought would kill all children of omxplayer, I don t know if using spawn like that is wrong or if it's the code that doesn't work.

The last edit I made was the good answer, but had to be edited a bit.

I created a sh file (with execute right) like this:

PID=$1
PGID=$(ps opgid= "$PID")
kill -QUIT -"$PGID"

Which I start like this:

execF('kill.sh', [child.pid], function(){
    console.log('killed');
});

Instead of child.kill.

I'm not sure if it s the best way to do, nor if the code is clean, but it does work.

I'll accept any answer which make it in a cleaner way or, even better, without having to execute a file.

Upvotes: 92

Views: 103404

Answers (12)

puchu
puchu

Reputation: 3652

Please use the following code in the latest lts node (v18).

child.stdin.end();
child.stdout.destroy();
child.stderr.destroy();
child.kill();

It is better to destroy stdout and stderr pipes forcibly before killing process. Otherwise these pipes will still be available after kill during several miliseconds. Some open handler detectors may fail, for example jest:

Jest has detected the following 1 open handle potentially keeping Jest from exiting:

  ●  PIPEWRAP

Upvotes: 3

Chris Pavs
Chris Pavs

Reputation: 205

Combining and clarifying answers from @Superdrac + @Evil.

If you're running something that has an exit command, just try using that command directly.

For example, most programs take q + enter to exit. So you can run:

child.stdin.write("q\n");

and the process will interpret that the same way, allowing it to clean up itself.

Upvotes: 0

slhck
slhck

Reputation: 38652

An alternative that might not exactly suit your use case is setting the detached option to true and ignoring stdin. Then, you can later kill your process using its PID.

const process = child_process.spawn(
  'omxplayer', ['test.mp4'],
  {
    detached: true,
    stdio: ['ignore', 'pipe', 'pipe'],
  }
);

setTimeout(() => {
  child_process.spawnSync(
    'kill', [process.pid.toString()]
  );
}, 1200);

Upvotes: 0

Newbie
Newbie

Reputation: 4819

In node:16 I was required to destroy() (pause() i not enough) the std channels before sending kill(). Like this:

cmd.stdout.destroy();
cmd.stderr.destroy();
cmd.kill('SIGINT');

See how exec() source code handles kill()

Upvotes: 4

etcc
etcc

Reputation: 41

You must specify the signal:

child.kill('SIGKILL')

https://nodejs.org/api/child_process.html#child_process_subprocess_kill_signal

Upvotes: 3

Michael Hall
Michael Hall

Reputation: 3330

There is a really neat npm package called tree-kill which does this very easily and effectively. It kills the child process, and all child processes that child may have started.

var kill  = require('tree-kill');
const spawn = require('child_process').spawn;

var scriptArgs = ['myScript.sh', 'arg1', 'arg2', 'youGetThePoint'];
var child = spawn('sh', scriptArgs);

// some code to identify when you want to kill the process. Could be
// a button on the client-side??
button.on('someEvent', function(){
    // where the killing happens
    kill(child.pid);
});

Upvotes: 64

Stinger112
Stinger112

Reputation: 11

Try to use child_process.execFile() method from here.

The child_process.execFile() function is similar to child_process.exec() except that it does not spawn a shell. Rather, the specified executable file is spawned directly as a new process making it slightly more efficient than child_process.exec().

It works in my case.

Upvotes: 0

Superdrac
Superdrac

Reputation: 1208

Why don't you just send the 'q' value in the stdin pipe ? It kill the omxplayer process.

Upvotes: 5

DrakaSAN
DrakaSAN

Reputation: 7853

Finally, I found how to do it without script:

exec('pkill omxplayer', function(err, stdout, stderr){
    if (stdout){console.log('stdout:'+stdout);}
    if (stderr){console.log('stderr:'+stderr);}
    if (err){throw err;}
    //...
}

Upvotes: 1

mikenolan
mikenolan

Reputation: 121

I've had exactly the same issue as you with omxplayer and the solution in this blog post worked for me.

var psTree = require('ps-tree');

var kill = function (pid, signal, callback) {
    signal   = signal || 'SIGKILL';
    callback = callback || function () {};
    var killTree = true;
    if(killTree) {
        psTree(pid, function (err, children) {
            [pid].concat(
                children.map(function (p) {
                    return p.PID;
                })
            ).forEach(function (tpid) {
                try { process.kill(tpid, signal) }
                catch (ex) { }
            });
            callback();
        });
    } else {
        try { process.kill(pid, signal) }
        catch (ex) { }
        callback();
    }
};

// elsewhere in code
kill(child.pid);

Upvotes: 12

robinkc
robinkc

Reputation: 1348

Refer to this discussion

Once you start listening for data on stdin, node will wait for the input on stdin until it is told not to. When either user presses ctrl-d (meaning end of input) or the program calls stdin.pause(), node stops waiting on stdin.

A node program does not exit unless it has nothing to do or wait for. Whats happening is, it is waiting on stdin and therefore never exits.

Try changing your setTimeout callback to

console.log('kill');
child.stdin.pause();
child.kill();

I hope that should work.

Upvotes: 79

apscience
apscience

Reputation: 7243

You've spawned a child process which was successfully killed. However, your main thread is still executing, which is why you have to press Ctrl+C.

Upvotes: 1

Related Questions