[Pkg-javascript-commits] [node-foreground-child] 53/69: Let the child decide if signals should be fatal
Bastien Roucariès
rouca at moszumanska.debian.org
Fri Aug 25 11:43:07 UTC 2017
This is an automated email from the git hooks/post-receive script.
rouca pushed a commit to branch master
in repository node-foreground-child.
commit cdd8a36d881405a63d7afe6c03622f387a1bda63
Author: isaacs <i at izs.me>
Date: Thu Dec 15 16:15:57 2016 -0800
Let the child decide if signals should be fatal
The use of signal-exit in this module was incorrect.
If the parent did not set any handlers on, for example, SIGTERM, then if
it receives a SIGTERM, it would conclude that it should be fatal. The
signal would be proxied on to the child, and then signal-exit would
remove the handlers from the parent, and re-send the signal to exit
appropriately. In this case, if the child was listening for that signal
and decided not to exit, then the parent's exit would be inappropriate.
If the parent _had_ assigned listeners to SIGTERM, then signal-exit
would conclude that the signal is _not_ fatal, and do nothing. That
also means that the signal would not be proxied to the child process at
all!
As of this patch, the parent attaches listeners to all would-be fatal
signals for the current operating system (which signal-exit helpfully
provides for us), but still only exits when and if the child exits.
Note that a SIGKILL will always kill the parent process, _and never the
child process_, because SIGKILL cannot be caught or proxied. The only
way to do this would be if Node provided a way to truly exec a process
as the new foreground program in the same process space, without forking
a separate child process.
---
README.md | 6 +++++
index.js | 25 ++++++++++++++---
test/immortal-child.js | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 101 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 1c45a1c..d388f95 100644
--- a/README.md
+++ b/README.md
@@ -44,3 +44,9 @@ into a child process. In these cases, foreground-child will not map
the file descriptors into the child. If file descriptors 0, 1, or 2
are used for the IPC channel, then strange behavior may happen (like
printing IPC messages to stderr, for example).
+
+Note that a SIGKILL will always kill the parent process, _and never
+the child process_, because SIGKILL cannot be caught or proxied. The
+only way to do this would be if Node provided a way to truly exec a
+process as the new foreground program in the same process space,
+without forking a separate child process.
diff --git a/index.js b/index.js
index aa48c6f..837793d 100644
--- a/index.js
+++ b/index.js
@@ -39,15 +39,16 @@ module.exports = function (program, args, cb) {
var child = spawn(program, args, spawnOpts)
var childExited = false
- signalExit(function (code, signal) {
- child.kill(signal || 'SIGHUP')
- })
+ var unproxySignals = proxySignals(child)
+ process.on('exit', childHangup)
child.on('close', function (code, signal) {
// Allow the callback to inspect the child’s exit code and/or modify it.
process.exitCode = signal ? 128 + signal : code
cb(function () {
+ unproxySignals()
+ process.removeListener('exit', childHangup)
childExited = true
if (signal) {
// If there is nothing else keeping the event loop alive,
@@ -78,3 +79,21 @@ module.exports = function (program, args, cb) {
return child
}
+
+function proxySignals (child) {
+ var listeners = {}
+ signalExit.signals().forEach(function (sig) {
+ listeners[sig] = function () {
+ child.kill(sig)
+ }
+ process.on(sig, listeners[sig])
+ })
+
+ return unproxySignals
+
+ function unproxySignals () {
+ for (var sig in listeners) {
+ process.removeListener(sig, listeners[sig])
+ }
+ }
+}
diff --git a/test/immortal-child.js b/test/immortal-child.js
new file mode 100644
index 0000000..d66e057
--- /dev/null
+++ b/test/immortal-child.js
@@ -0,0 +1,73 @@
+var node = process.execPath
+
+if (process.argv[2] === 'child')
+ child()
+else if (process.argv[2] === 'parent')
+ parent()
+else
+ test()
+
+function test () {
+ var t = require('tap')
+ var spawn = require('child_process').spawn
+ var proc = spawn(node, [__filename, 'parent'])
+
+ var out = ''
+ proc.stdout.on('data', function (c) { out += c })
+
+ var err = ''
+ proc.stderr.on('data', function (c) { err += c })
+
+ proc.on('exit', function (code, signal) {
+ clearTimeout(timer)
+ })
+ proc.on('close', function (code, signal) {
+ var actual = {
+ out: out,
+ err: err,
+ code: code,
+ signal: signal
+ }
+ var expect = {
+ out: /^(child alive\n)*child SIGINT received\n(child alive\n)*child exit null SIGTERM\n$/,
+ err: /^parent \d+\nchild \d+\n$/,
+ code: null,
+ signal: 'SIGTERM'
+ }
+ t.match(actual, expect)
+ t.end()
+ })
+
+ var time = 250
+ // coverage slows things down a bit
+ if (process.env._TAP_COVERAGE_)
+ time = 1000
+ var timer = setTimeout(function () {
+ proc.kill('SIGINT')
+ timer = setTimeout(function () {
+ proc.kill('SIGTERM')
+ }, time)
+ }, time)
+}
+
+function parent () {
+ console.error('parent', process.pid)
+ var fg = require('../')
+ fg(node, [ __filename, 'child' ])
+}
+
+function child () {
+ console.error('child', process.pid)
+ setInterval(function () {
+ console.log('child alive')
+ }, 200)
+ process.on('SIGINT', function () {
+ console.log('child SIGINT received')
+ })
+ process.on('SIGHUP', function () {
+ console.log('child SIGHUP received')
+ })
+ require('signal-exit')(function (code, signal) {
+ console.log('child exit', code, signal)
+ })
+}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-foreground-child.git
More information about the Pkg-javascript-commits
mailing list