12

The following will never exit

var child_process = require('child_process');

var ps = child_process.spawn('.\\node_modules\\.bin\\babel.cmd', ['input.js', '--out-file', 'output.js', '--watch']);

ps.on('exit', function() {
  console.log('exit');
});

ps.on('close', function() {
  console.log('close');
});

setTimeout(function () {
  ps.kill();
}, 2000);

What's going on here? also what is the correct thing to do here? The only way to make this process actually close is to kill the parent process. I suspect it's waiting for stdio to flush or something like that?

It does die if it is given an 'ignore' stdio configuration, but I need the streams.

var ps = child_process.spawn('.\\node_modules\\.bin\\babel.cmd', ['test.js', '--out-file', 'output.js', '--watch'], {
    stdio: 'ignore'
});
Casper Beyer
  • 2,203
  • 2
  • 22
  • 35
  • Those this post help: http://stackoverflow.com/a/20189473/121946 – Adrian Lynch Sep 21 '15 at 23:03
  • 1
    @AdrianLynch Not really, the highest voted answer just points to `child_process.kill`. The process won't die from that alone when using a shell wrapper. – Casper Beyer Sep 21 '15 at 23:16
  • Perhaps something's catching the default SIGTERM (https://github.com/nodejs/node/blob/master/lib/internal/child_process.js#L351-L357) signal. If you want it to be killed immediately you could try `ps.kill('SIGKILL')`. – Matt Harrison Sep 24 '15 at 10:46
  • 1
    Pretty sure I ran through SIGKILL, SIGTERM and SIGINT but I'll double check. – Casper Beyer Sep 24 '15 at 10:52
  • Maybe give us more context about what you're actually trying to do? – pilau Sep 24 '15 at 11:11
  • Trying to get [Amok](http://amokjs.com/introduction)'s test suite to reliably pass on Windows. Some of which involves spawning child processes for babel, coffee, etc. Now since these child processes don't actually die the tests will just stall, forever. Since the child process keeps the parent alive and the parent keeps the child process alive. Unix does not see this behavior at all and has been happily passing for months. – Casper Beyer Sep 24 '15 at 11:13
  • The type of signal sent does not matter tho, its definitively hanging because of stdio. I suppose because killing a cmd does not mean any processes within that will be killed? so the cmd wrapper is terminating but the node process it spawns does not? – Casper Beyer Sep 24 '15 at 11:22

5 Answers5

29

You are spawning child process cmd.exe by using babel.cmd. This process then starts another grandchild process node and runs babel script. When you do ps.kill() it only kills the cmd.exe but not the child processes created by it.

Although cmd.exe is closed, the stdio stream of the parent process is still shared with the other processes in the tree. So the parent process is waiting for the stream to be closed. Using stdio: 'ignore' will only stop sharing the stdio stream with other processes in the tree but those grandchild processes will still continue running.


Update:

After discussing with OP, I tested with all three stdio options: pipe(default), inherit, ignore in iojs 3.3.0, Windows 7 (32bit). None of them kills the grandchild process.

With inherit and ignore, both parent process and child process(cmd.exe) are killed but the grand child process still floats around and doesn't belong to any process tree.

With pipe, only the child process is terminated but both parent and grand child process continue running. The reason that parent process is not exiting is most likely because the stdio of parent node.exe is still shared with the other processes as mentioned in original answer.

child.unref() doesn't have any effect on killing grand child process. It only removes the child process from parent's event loop so that the parent process can exit normally.


I could think of couple of solutions:

  1. Invoke the babel.js directly without that cmd script:

    var ps = child_process.spawn('node', ['.\\node_modules\\babel\\bin\\babel.js', 'input.js', '--out-file', 'output.js', '--watch'])
    
  2. Use windows taskkill command with \T flag to kill the process and all the child processes started by it:

    os = require('os');
    if(os.platform() === 'win32'){
        child_process.exec('taskkill /pid ' + ps.pid + ' /T /F')
    }else{
        ps.kill();  
    }
    

    There are some npm modules that can handle killing child processes in both Unix and Windows e.g. tree-kill. For windows it uses taskkill or tasklist.

hassansin
  • 16,918
  • 3
  • 43
  • 49
  • Both cmd.exe and its child node.exe do get killed however when looking at the process tree..? – Casper Beyer Sep 29 '15 at 00:31
  • That's probably the parent node.exe. How are you observing tree? You can use Process explorer. – hassansin Sep 29 '15 at 00:39
  • The parent node.exe is sticking around so no, for sure the child process cmd.exe and its child node.exe is being killed (Yes observing with process explorer) – Casper Beyer Sep 29 '15 at 00:42
  • Does your script do other things besides running babel command? I did run your example script with the fix and all processes terminate normally. – hassansin Sep 29 '15 at 00:51
  • I was referring to just the reduced test case with stdio: 'pipe'. Taskkill would work but isn't that just a hack around the problem? Why does the process (grandchild node) seem to terminate with 'inherit' but not with 'pipe'? Lets make this answer better. Go into more of the why instead of the workarounds. You can also unref the child process which will let the parent exit thus killing everything. – Casper Beyer Sep 29 '15 at 00:59
  • Unfortunately I can't reproduce what you are referring to. With the given test case and `stdio:'pipe'`, both the parent and grandchild node.exe stick around on my process tree. Only difference is that, grandchild node.exe now moves to the top of the process tree as it's parent cmd.exe has been killed. And I explained this behavior in my answer. – hassansin Sep 29 '15 at 01:13
  • Awarded the bounty, as it was about to expire and there are no other answers. However, as the answer stands now has a different conclusion than my test runs with node v4. – Casper Beyer Oct 01 '15 at 04:54
  • Actually, no you are right. cmd.exe is killed and node.exe is orphaned completely becoming a top level process. – Casper Beyer Oct 01 '15 at 05:02
  • What about process groups? Windows certainly supports them via CreateProcess and forwards signals to all descendants of the group. – Casper Beyer Oct 02 '15 at 06:40
  • I know Windows doesn't support signals in the same manner as unix but sigint should be propagated? – Casper Beyer Oct 03 '15 at 04:57
  • Look like for windows [TerminateProcess](https://github.com/nodejs/node/blob/b5cd2f098691935b6bef6ded1b0de7ef37431f27/deps/uv/src/win/process.c#L1176) is the function that kills a process. And [this](http://stackoverflow.com/questions/12485379/i-am-not-able-to-kill-a-child-process-using-terminateprocess) suggests it can't kill child process? – hassansin Oct 03 '15 at 14:29
  • found this working on a problem getting a supervisor project folder restarter running on a win10 computer, after trying a bunch of other things, tree-kill was a big help – stackuser83 Jul 26 '17 at 20:20
4

I have the same issue on Windows (Win 10 64x). I can't terminate a process of a spawned child process.

I start a service (custom HTTP server service.windows) using child_process.spawn():

const { spawn } = require('child_process');
let cp = spawn('"C:\\Users\\user\\app\\service.windows"', [], { shell: true,  });

cp.stderr.on('data', (data) => {
    console.log(`stderr: ${data}`);
    console.log('cp.connected', cp.connected);
    console.log('process.pid', process.pid); // 6632 <<= main node.js PID
    console.log('cp.pid', cp.pid); // 9424 <<= child PID

    // All these are useless, they just kill `cp`
    cp.kill('SIGINT'); // Doesn't terminate service.windows
    cp.kill('SIGKILL'); // Doesn't terminate service.windows
    cp.kill('SIGTERM'); // Doesn't terminate service.windows
});

And I want to terminate my HTTP server service (service.windows). And it is not possible on Windows using cp.kill('ANY SIGNAL'). Node.js kills its child process (cp) but my HTTP server (service.windows) still runs fine.

When I check it in other terminal, I see my server is running just fine:

$ netstat -ano | findstr :9090
  TCP    0.0.0.0:9090           0.0.0.0:0              LISTENING       1340
  TCP    [::]:9090              [::]:0                 LISTENING       1340

I try manually kill my server by PID but with T flag, not F. The difference:

T terminate all child processes along with the parent process, commonly known as a tree kill.

F process(es) be forcefully terminated.

$ taskkill -T -PID 1340
ERROR: The process with PID 1340 (child process of PID 9424) could not be terminated.
Reason: This process can only be terminated forcefully (with /F option).

And it exactly says that my server 1340 is a child of that cp - PID 9424.

OK, I try to terminate that cp using T flag again. And boom, not possible. cp is a child of my main node.js process.pid 6632:

$ taskkill -T -PID 9424
ERROR: The process with PID 1340 (child process of PID 9424) could not be terminated.
Reason: This process can only be terminated forcefully (with /F option).
ERROR: The process with PID 9424 (child process of PID 6632) could not be terminated.
Reason: One or more child processes of this process were still running.

I can only kill it forcefully with F flag:

$ taskkill -F -T -PID 9424
SUCCESS: The process with PID 1340 (child process of PID 9424) has been terminated.
SUCCESS: The process with PID 9424 (child process of PID 6632) has been terminated.

Most disappointing thing is that Node.js docs doesn't say jack ship about how to deal with this issue. They only say "yes, we know the issue exist and we just inform you about it".

The only option I see on Windows is to use taskkill -F -T -PID 9424 in spawned process:

exec('taskkill -F -T -PID 9424');
Green
  • 28,742
  • 61
  • 158
  • 247
1

One potential solution is to call ReadableStream.end or ReadableStream.destroy

Casper Beyer
  • 2,203
  • 2
  • 22
  • 35
1

The most straight forward solution, don't spawn scripts instead spawn node directly.

For unix, the script is the npm executable. For windows however we need to resolve the name from the .cmd file.

if (path.extname(command).toLowerCase() == '.cmd') {
  var content = '' + fs.readFileSync(command);
  var link = (/node  "%~dp0\\(.*?)"/.exec(content) || [])[1];

  if (link) {
    command = path.resolve(path.dirname(command), link);
  }
}

var ps = child.spawn('node', [command].concat(args), options);
Casper Beyer
  • 2,203
  • 2
  • 22
  • 35
0

For me, my process was spawn of cmd.exe with the args:

/s /c start /B /I /belownormal ffmpeg.exe -hide_banner -i somevideo.mp4 -vn -ar 48000 -ac 2 -c:a pcm_s16le -f wav -

I tried most of the suggested answers, but what solved it was calling child.stdin.destroy();
child.stderr.destroy();
child.stdout.destroy();
This could be because I was explicitly using stdout to get data from the process..

I still call child.kill('SIGINT'); for good measure.
taskkill gave me 'pid not found'.

(node 12).

Simon H
  • 373
  • 4
  • 7