Skip to content

Commit

Permalink
process: add one-shot signal handler support
Browse files Browse the repository at this point in the history
`one-shot` signal handlers reset the handler once the signal is received
and not when the signal notification reaches the main loop. This commit
adds support for this functionality when *only* there are `once`
listeners on a specific signal.

Refs: nodejs#9050
Refs: libuv/libuv#1104
Refs: libuv/libuv#1106
  • Loading branch information
santigimeno committed Dec 2, 2017
1 parent 52f6ab8 commit e403bae
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 19 deletions.
63 changes: 45 additions & 18 deletions lib/internal/process.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,37 +181,64 @@ function setupSignalHandlers() {
// Load events module in order to access prototype elements on process like
// process.addListener.
const signalWraps = {};
const oneshots = {};

function isSignal(event) {
return typeof event === 'string' && constants[event] !== undefined;
}

// Detect presence of a listener for the special signal types
process.on('newListener', function(type, listener) {
if (isSignal(type) &&
!signalWraps.hasOwnProperty(type)) {
const Signal = process.binding('signal_wrap').Signal;
const wrap = new Signal();
function startSignalWrap(type, isOneShot) {
if (signalWraps[type]) {
signalWraps[type].close();
delete signalWraps[type];
}

wrap.unref();
const Signal = process.binding('signal_wrap').Signal;
const wrap = new Signal();
wrap.unref();

wrap.onsignal = function() { process.emit(type, type); };
wrap.onsignal = function() { process.emit(type, type); };

const signum = constants[type];
const err = wrap.start(signum);
if (err) {
wrap.close();
throw util._errnoException(err, 'uv_signal_start');
const signum = constants[type];
const err = wrap.start(signum, isOneShot);
if (err) {
wrap.close();
throw util._errnoException(err, 'uv_signal_start');
}

signalWraps[type] = wrap;
}

// Detect presence of a listener for the special signal types
process.on('newListener', function(type, listener, isOnce) {
if (isSignal(type)) {
if (isOnce) {
if (oneshots[type])
++oneshots[type];
else
oneshots[type] = 1;
}

signalWraps[type] = wrap;
if (!signalWraps[type] ||
!isOnce && this.listenerCount(type) === oneshots[type]) {
startSignalWrap(type, isOnce);
}
}
});

process.on('removeListener', function(type, listener) {
if (signalWraps.hasOwnProperty(type) && this.listenerCount(type) === 0) {
signalWraps[type].close();
delete signalWraps[type];
process.on('removeListener', function(type, listener, isOnce) {
if (isOnce)
--oneshots[type];

if (signalWraps[type]) {
const count = this.listenerCount(type);
if (count === 0) {
signalWraps[type].close();
delete signalWraps[type];
delete oneshots[type];
} else if ((count === oneshots[type]) && !isOnce) {
startSignalWrap(type, true);
}
}
});
}
Expand Down
7 changes: 6 additions & 1 deletion src/signal_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

namespace node {

using v8::Boolean;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
Expand Down Expand Up @@ -87,6 +88,8 @@ class SignalWrap : public HandleWrap {
SignalWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
int signum = args[0]->Int32Value();
CHECK(args[1]->IsBoolean());
bool oneshot = args[1].As<Boolean>()->Value();
#if defined(__POSIX__) && HAVE_INSPECTOR
if (signum == SIGPROF) {
Environment* env = Environment::GetCurrent(args);
Expand All @@ -97,7 +100,9 @@ class SignalWrap : public HandleWrap {
}
}
#endif
int err = uv_signal_start(&wrap->handle_, OnSignal, signum);
int err = oneshot ?
uv_signal_start_oneshot(&wrap->handle_, OnSignal, signum) :
uv_signal_start(&wrap->handle_, OnSignal, signum);
args.GetReturnValue().Set(err);
}

Expand Down
70 changes: 70 additions & 0 deletions test/parallel/test-signal-oneshot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const spawn = require('child_process').spawn;

if (common.isWindows)
common.skip('Sending signals is not supported');

if (process.argv[2] === 'child') {
if (process.argv[3] === 'once') {
const on_sigint = () => {};
process.on('SIGINT', on_sigint);
process.once('SIGINT', () => {});
process.removeListener('SIGINT', on_sigint);
process.once('message', common.mustCall((msg) => {
assert.strictEqual(msg, 'signal');
process.send('signal');
while (true) {}
}));
} else if (process.argv[3] === 'on') {
let signals = 0;
process.once('SIGINT', common.mustCall(() => {
process.send('signal');
}));

process.on('SIGINT', common.mustCall(() => {
if (++signals === 2)
process.exit(0);
}, 2));
}

process.send('signal');
setTimeout(() => {}, 100000);

return;
}

{
const child = spawn(process.execPath, [__filename, 'child', 'once'], {
stdio: ['pipe', 'pipe', 'pipe', 'ipc']
});

child.on('exit', common.mustCall((code, signal) => {
assert.strictEqual(code, null);
assert.strictEqual(signal, 'SIGINT');
}));

child.on('message', (msg) => {
assert.strictEqual(msg, 'signal');
child.kill('SIGINT');
child.send('signal');
});
}

{
const child = spawn(process.execPath, [__filename, 'child', 'on'], {
stdio: ['pipe', 'pipe', 'pipe', 'ipc']
});

child.on('exit', common.mustCall((code, signal) => {
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
}));

child.on('message', (msg) => {
assert.strictEqual(msg, 'signal');
child.kill('SIGINT');
});
}

0 comments on commit e403bae

Please sign in to comment.