Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

child_process.spawn sh not killable #2098

Closed
paulpflug opened this issue Jul 3, 2015 · 20 comments
Closed

child_process.spawn sh not killable #2098

paulpflug opened this issue Jul 3, 2015 · 20 comments
Labels
child_process Issues and PRs related to the child_process subsystem. doc Issues and PRs related to the documentations.

Comments

@paulpflug
Copy link

Comming from here:
darkguy2008/parallelshell#22

(btw: Usecase for #1009)

Everytime I need to spawn a process I do it like this

if (process.platform === 'win32') {
    sh = 'cmd';
    shFlag = '/c';
} else {
    sh = 'sh';
    shFlag = '-c';
}
var child = spawn(sh,[shFlag,cmd], {
  cwd: process.cwd,
  env: process.env,
  stdio: ['pipe', process.stdout, process.stderr]
})

The problem: child is unkillable on unix.
Example in coffee-script:

spawn = require("child_process").spawn
child = spawn "sh", ["-c", "node -e 'setTimeout(function(){},10000);'"]
child.on "close", process.exit
child.kill()
child.kill("SIGINT")
child.kill("SIGTERM")
child.kill("SIGHUP")
spawn "sh",["-c","kill -TERM "+child.pid]
spawn "sh",["-c","kill -INT "+child.pid]
spawn "sh",["-c","kill -HUP "+child.pid]

(on windows it works fine with sh replaced by cmd)
Even when I exit the process with process.exit(), the child stays alive.

Workaround I found:

spawn = require("child_process").spawn
child = spawn "sh", ["-c", "node -e 'setTimeout(function(){},10000);'"], detached: true
child.on "close", process.exit
spawn "sh",["-c","kill -INT -"+child.pid]

Killing by pgid works, by pid works not. Sending ^C from console works also, I assume it uses pgid always.

@bnoordhuis
Copy link
Member

I don't think that's a bug. Your example sends signals to sh, not node. sh may elect to ignore SIGINT, SIGTERM, etc.

The reason it works with { detached: true } is that it creates a new session group, i.e., it calls setsid(2). In that case, PID == PGID, and sending a signal will deliver it to all processes in the group.

@paulpflug
Copy link
Author

But with detached: false I would expect sh to get cleaned up on process.exit()

That should be the point of the detached option?

@bnoordhuis
Copy link
Member

I think you have the wrong idea about processes and process groups. On exit, child processes get reparented, they're not forcibly killed by io.js or the kernel.

{ detached: true } turns the child process into a group leader, that's really all it does.

@paulpflug
Copy link
Author

From the node docs: options.detached

This makes it possible for the child to continue running after the parent exits.

What I read from that sentence: if detached = false the child get force killed

On exit, child processes get reparented, they're not forcibly killed by io.js or the kernel.

What you say is, that the child is always continuing after the parent exits, regardless of detached
I would definitivly expect different behavior as a user.
At least, we have to alter the docs 😄

@bnoordhuis
Copy link
Member

The documentation for options.detached is not wrong as such, but it's not the whole story.

What you say is, that the child is always continuing after the parent exits, regardless of detached

Not quite. { detached: true } means, among other things, that the child process won't receive a SIGINT when you kill the parent with ^C.

/cc @nodejs/documentation - the documentation can probably be improved, although I'm not sure where to draw the line between inline documentation and pointing people to a UNIX process management primer.

@okdistribute
Copy link

@paulpflug what exact platform were you using to test this? (any more specific than unix?) I found the same problem, works on mac but not on ubuntu. Seems to close properly if I use /bin/bash instead, got the idea from #4432

@paulpflug
Copy link
Author

@Karissa ubuntu ;) I'm unsure of the reliability of bash

currently I live with using detached: process.platform != "win32" on spawn
and killing by gpid

if (process.platform != "win32") {
  spawn("sh", ["-c", "kill -INT -"+child.pid]);
  } else {
  child.kill("SIGINT");
}

I would really appreciate a more beautiful solution, but found none so far.
Thanks for pointing out, though.

@sam-github
Copy link
Contributor

@paulpflug If you want to send a signal to an entire process group, using detached (on non-windows) to put the child and its descendants in a group is exactly what unix process gouping is intended for.

Your spawning of kill should be unnecessary, use process.kill(-child.pid, 'SIGINT') - this should work, and if it doesn't, file a bug report!

@paulpflug
Copy link
Author

@sam-github that is cool, didn't know that. And it does work 👍

I created a small wrapper: better-spawn

@cjihrig
Copy link
Contributor

cjihrig commented Feb 13, 2016

This isn't Node specific, but it might be something worth mentioning in the documentation.

@jasnell
Copy link
Member

jasnell commented Apr 3, 2016

@nodejs/documentation

@eljefedelrodeodeljefe
Copy link
Contributor

Though I agree with the general "processes on the different platforms" discussion, I think I can confirm a bug here:
Actually there could be a number of bugs. Most significant, likely only one, is that child.pid is off by -1 in the following example. Adding +1 manually fixes it. On OS X it works fine without the addition. Listening for SIGHUP is optional and only demonstrates, that sending and catching signals works.

'use strict'
const spawn = require('child_process').spawn

console.log('Spawning from:', process.pid)

let child = spawn('sh', ['-c',
  `node -e "

  process.on('SIGHUP', () => {console.log('received SIGHUP', process.exit())})

  setInterval(() => {
      console.log(process.pid, 'is alive')
    }, 500);"`
  ], {
    stdio: ['inherit', 'inherit', 'inherit']
  })

child.on('close', () => {
  console.log('caught child\'s exit')
  // exiting here should be unecessary
  process.exit()
})

setTimeout(() => {
  // child.kill()
  // child.kill('SIGINT')
  // child.kill('SIGTERM')
  // child.kill('SIGHUP')
  console.log(child.pid)
  process.kill(child.pid + 1, 'SIGHUP')
}, 2000)

This has the following output.

Spawning from: 32656
32662 'is alive'
32662 'is alive'
32662 'is alive'
32661
caught child's exit

@eljefedelrodeodeljefe
Copy link
Contributor

Scratch what I said. It's the pid of sh that causes the offset. Hence it's only natural to increment by one. This then could only lead to such misunderstandings, also with the new shell option. Gonna crate a PR for spawning shells and kill on linux, if there are no objections to the contents in the PR.

For sh not being killable linux specific process implementation applies.

Here is the output of ps auxfor the above.

paralle+  3466  1.0  0.1 637396 15988 pts/2    Sl+  12:06   0:00 ./node app.js
paralle+  3474  0.0  0.0   4440   656 pts/2    S+   12:06   0:00 sh -c node -e "    process.on('SIGHUP', () => {con
paralle+  3475  0.5  0.1 641360 14988 pts/2    Sl+  12:06   0:00 node -e     process.on('SIGHUP', () => {console.lo
paralle+  3519  0.0  0.0  22640  1312 pts/1    R+   12:06   0:00 ps aux

@Knighton910
Copy link

+1 @eljefedelrodeodeljefe

Gonna crate a PR for spawning shells and kill on linux, if there are no objections to the contents in the PR.

@benjamingr
Copy link
Member

Closed by PR by @eljefedelrodeodeljefe 🎉

@eljefedelrodeodeljefe
Copy link
Contributor

Ping, everyone interested I have sent in a PR at libuv to land something we could implement as

let clds = process.children();
// => [ 5598, 5601]

clds.forEach((pid) => {
   process.kill(pid);
});

Happy for initial thoughts (in light of the problem here).

@stevemao
Copy link
Contributor

@eljefedelrodeodeljefe can you link it here?

@eljefedelrodeodeljefe
Copy link
Contributor

Sure. See above also or here libuv/libuv#836

@benjamingr
Copy link
Member

+1 for adding the .children() part to the docs and to the NodeJS API.

MylesBorins pushed a commit that referenced this issue Apr 19, 2016
This commit refines the documentation around child.kill(), where kill
attempts against shells will lead to unexpected results. Namely, on
linux the child process of a child process will not terminate, when
its parent gets terminated. This is different across the the
platforms.

PR-URL: #2098
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Closes: #2098
MylesBorins pushed a commit that referenced this issue Apr 20, 2016
This commit refines the documentation around child.kill(), where kill
attempts against shells will lead to unexpected results. Namely, on
linux the child process of a child process will not terminate, when
its parent gets terminated. This is different across the the
platforms.

PR-URL: #2098
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Closes: #2098
MylesBorins pushed a commit that referenced this issue Apr 20, 2016
This commit refines the documentation around child.kill(), where kill
attempts against shells will lead to unexpected results. Namely, on
linux the child process of a child process will not terminate, when
its parent gets terminated. This is different across the the
platforms.

PR-URL: #2098
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Closes: #2098
This was referenced Apr 21, 2016
MylesBorins pushed a commit that referenced this issue Apr 21, 2016
This commit refines the documentation around child.kill(), where kill
attempts against shells will lead to unexpected results. Namely, on
linux the child process of a child process will not terminate, when
its parent gets terminated. This is different across the the
platforms.

PR-URL: #2098
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Closes: #2098
jasnell pushed a commit that referenced this issue Apr 26, 2016
This commit refines the documentation around child.kill(), where kill
attempts against shells will lead to unexpected results. Namely, on
linux the child process of a child process will not terminate, when
its parent gets terminated. This is different across the the
platforms.

PR-URL: #2098
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Closes: #2098
MylesBorins pushed a commit that referenced this issue May 17, 2016
This commit refines the documentation around child.kill(), where kill
attempts against shells will lead to unexpected results. Namely, on
linux the child process of a child process will not terminate, when
its parent gets terminated. This is different across the the
platforms.

PR-URL: #2098
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Closes: #2098
MylesBorins pushed a commit that referenced this issue May 18, 2016
This commit refines the documentation around child.kill(), where kill
attempts against shells will lead to unexpected results. Namely, on
linux the child process of a child process will not terminate, when
its parent gets terminated. This is different across the the
platforms.

PR-URL: #2098
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Closes: #2098
@leo
Copy link

leo commented Jul 12, 2016

My god!

Thank you so much for bringing this up, guys. I've spent countless hours trying to figure out why my child process didn't receive these events – it's fixed now.

🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
child_process Issues and PRs related to the child_process subsystem. doc Issues and PRs related to the documentations.
Projects
None yet
Development

Successfully merging a pull request may close this issue.