mirror of https://github.com/lukechilds/node.git
Browse Source
- Adds the `breakEvalOnSigint` option to `vm.runIn(This)Context`. This uses a watchdog thread to wait for SIGINT and generally works just like the existing `timeout` option. - Adds a method to the existing timer-based watchdog to check if it stopped regularly or by running into the timeout. This is used to tell a SIGINT abort from a timer-based one. - Adds (internal) `process._{start,stop}SigintWatchdog` methods to start/stop the watchdog thread used by the above option manually. This will be used in the REPL to set up SIGINT handling before entering terminal raw mode, so that there is no time window in which Ctrl+C fully aborts the process. PR-URL: https://github.com/nodejs/node/pull/6635 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>v6.x
committed by
Jeremiah Senkpiel
16 changed files with 582 additions and 11 deletions
@ -0,0 +1,53 @@ |
|||
'use strict'; |
|||
const common = require('../common'); |
|||
const assert = require('assert'); |
|||
const binding = process.binding('util'); |
|||
|
|||
if (process.platform === 'win32') { |
|||
// No way to send CTRL_C_EVENT to processes from JS right now.
|
|||
common.skip('platform not supported'); |
|||
return; |
|||
} |
|||
|
|||
[(next) => { |
|||
// Test with no signal observed.
|
|||
binding.startSigintWatchdog(); |
|||
const hadPendingSignals = binding.stopSigintWatchdog(); |
|||
assert.strictEqual(hadPendingSignals, false); |
|||
next(); |
|||
}, |
|||
(next) => { |
|||
// Test with one call to the watchdog, one signal.
|
|||
binding.startSigintWatchdog(); |
|||
process.kill(process.pid, 'SIGINT'); |
|||
setTimeout(common.mustCall(() => { |
|||
const hadPendingSignals = binding.stopSigintWatchdog(); |
|||
assert.strictEqual(hadPendingSignals, true); |
|||
next(); |
|||
}), common.platformTimeout(100)); |
|||
}, |
|||
(next) => { |
|||
// Nested calls are okay.
|
|||
binding.startSigintWatchdog(); |
|||
binding.startSigintWatchdog(); |
|||
process.kill(process.pid, 'SIGINT'); |
|||
setTimeout(common.mustCall(() => { |
|||
const hadPendingSignals1 = binding.stopSigintWatchdog(); |
|||
const hadPendingSignals2 = binding.stopSigintWatchdog(); |
|||
assert.strictEqual(hadPendingSignals1, true); |
|||
assert.strictEqual(hadPendingSignals2, false); |
|||
next(); |
|||
}), common.platformTimeout(100)); |
|||
}, |
|||
() => { |
|||
// Signal comes in after first call to stop.
|
|||
binding.startSigintWatchdog(); |
|||
binding.startSigintWatchdog(); |
|||
const hadPendingSignals1 = binding.stopSigintWatchdog(); |
|||
process.kill(process.pid, 'SIGINT'); |
|||
setTimeout(common.mustCall(() => { |
|||
const hadPendingSignals2 = binding.stopSigintWatchdog(); |
|||
assert.strictEqual(hadPendingSignals1, false); |
|||
assert.strictEqual(hadPendingSignals2, true); |
|||
}), common.platformTimeout(100)); |
|||
}].reduceRight((a, b) => common.mustCall(b).bind(null, a))(); |
@ -0,0 +1,77 @@ |
|||
'use strict'; |
|||
const common = require('../common'); |
|||
const assert = require('assert'); |
|||
const vm = require('vm'); |
|||
|
|||
const spawn = require('child_process').spawn; |
|||
|
|||
if (process.platform === 'win32') { |
|||
// No way to send CTRL_C_EVENT to processes from JS right now.
|
|||
common.skip('platform not supported'); |
|||
return; |
|||
} |
|||
|
|||
if (process.argv[2] === 'child') { |
|||
const parent = +process.env.REPL_TEST_PPID; |
|||
assert.ok(parent); |
|||
|
|||
let firstHandlerCalled = 0; |
|||
process.on('SIGINT', common.mustCall(() => { |
|||
firstHandlerCalled++; |
|||
// Handler attached _before_ execution.
|
|||
}, 2)); |
|||
|
|||
let onceHandlerCalled = 0; |
|||
process.once('SIGINT', common.mustCall(() => { |
|||
onceHandlerCalled++; |
|||
// Handler attached _before_ execution.
|
|||
})); |
|||
|
|||
assert.throws(() => { |
|||
vm.runInThisContext(`process.kill(${parent}, 'SIGUSR2'); while(true) {}`, { |
|||
breakOnSigint: true |
|||
}); |
|||
}, /Script execution interrupted/); |
|||
|
|||
assert.strictEqual(firstHandlerCalled, 0); |
|||
assert.strictEqual(onceHandlerCalled, 0); |
|||
|
|||
// Keep the process alive for a while so that the second SIGINT can be caught.
|
|||
const timeout = setTimeout(() => {}, 1000); |
|||
|
|||
let afterHandlerCalled = 0; |
|||
|
|||
process.on('SIGINT', common.mustCall(() => { |
|||
// Handler attached _after_ execution.
|
|||
if (afterHandlerCalled++ == 0) { |
|||
// The first time it just bounces back to check that the `once()`
|
|||
// handler is not called the second time.
|
|||
process.kill(parent, 'SIGUSR2'); |
|||
return; |
|||
} |
|||
|
|||
assert.strictEqual(onceHandlerCalled, 1); |
|||
assert.strictEqual(firstHandlerCalled, 2); |
|||
timeout.unref(); |
|||
}, 2)); |
|||
|
|||
process.kill(parent, 'SIGUSR2'); |
|||
|
|||
return; |
|||
} |
|||
|
|||
process.env.REPL_TEST_PPID = process.pid; |
|||
const child = spawn(process.execPath, [ __filename, 'child' ], { |
|||
stdio: [null, 'inherit', 'inherit'] |
|||
}); |
|||
|
|||
process.on('SIGUSR2', common.mustCall(() => { |
|||
// First kill() breaks the while(true) loop, second one invokes the real
|
|||
// signal handlers.
|
|||
process.kill(child.pid, 'SIGINT'); |
|||
}, 3)); |
|||
|
|||
child.on('close', function(code, signal) { |
|||
assert.strictEqual(signal, null); |
|||
assert.strictEqual(code, 0); |
|||
}); |
@ -0,0 +1,39 @@ |
|||
'use strict'; |
|||
const common = require('../common'); |
|||
const assert = require('assert'); |
|||
const vm = require('vm'); |
|||
|
|||
const spawn = require('child_process').spawn; |
|||
|
|||
if (process.platform === 'win32') { |
|||
// No way to send CTRL_C_EVENT to processes from JS right now.
|
|||
common.skip('platform not supported'); |
|||
return; |
|||
} |
|||
|
|||
if (process.argv[2] === 'child') { |
|||
const parent = +process.env.REPL_TEST_PPID; |
|||
assert.ok(parent); |
|||
|
|||
assert.throws(() => { |
|||
vm.runInThisContext(`process.kill(${parent}, "SIGUSR2"); while(true) {}`, { |
|||
breakOnSigint: true |
|||
}); |
|||
}, /Script execution interrupted/); |
|||
|
|||
return; |
|||
} |
|||
|
|||
process.env.REPL_TEST_PPID = process.pid; |
|||
const child = spawn(process.execPath, [ __filename, 'child' ], { |
|||
stdio: [null, 'pipe', 'inherit'] |
|||
}); |
|||
|
|||
process.on('SIGUSR2', common.mustCall(() => { |
|||
process.kill(child.pid, 'SIGINT'); |
|||
})); |
|||
|
|||
child.on('close', function(code, signal) { |
|||
assert.strictEqual(signal, null); |
|||
assert.strictEqual(code, 0); |
|||
}); |
Loading…
Reference in new issue