Browse Source

vm: add ability to break on sigint/ctrl+c

- 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
Anna Henningsen 9 years ago
committed by Jeremiah Senkpiel
parent
commit
d049919e7d
  1. 6
      doc/api/vm.md
  2. 47
      lib/vm.js
  3. 8
      src/node.cc
  4. 42
      src/node_contextify.cc
  5. 7
      src/node_internals.h
  6. 18
      src/node_util.cc
  7. 225
      src/node_watchdog.cc
  8. 59
      src/node_watchdog.h
  9. 3
      test/message/eval_messages.out
  10. 3
      test/message/stdin_messages.out
  11. 2
      test/message/undefined_reference_in_new_context.out
  12. 2
      test/message/vm_display_runtime_error.out
  13. 2
      test/message/vm_dont_display_runtime_error.out
  14. 53
      test/parallel/test-util-sigint-watchdog.js
  15. 77
      test/parallel/test-vm-sigint-existing-handler.js
  16. 39
      test/parallel/test-vm-sigint.js

6
doc/api/vm.md

@ -77,6 +77,12 @@ added: v0.3.1
* `timeout` {number} Specifies the number of milliseconds to execute `code`
before terminating execution. If execution is terminated, an [`Error`][]
will be thrown.
* `breakOnSigint`: if `true`, the execution will be terminated when
`SIGINT` (Ctrl+C) is received. Existing handlers for the
event that have been attached via `process.on("SIGINT")` will be disabled
during script execution, but will continue to work after that.
If execution is terminated, an [`Error`][] will be thrown.
Runs the compiled code contained by the `vm.Script` object within the given
`contextifiedSandbox` and returns the result. Running code does not have access

47
lib/vm.js

@ -13,6 +13,29 @@ const Script = binding.ContextifyScript;
// - isContext(sandbox)
// From this we build the entire documented API.
const realRunInThisContext = Script.prototype.runInThisContext;
const realRunInContext = Script.prototype.runInContext;
Script.prototype.runInThisContext = function(options) {
if (options && options.breakOnSigint) {
return sigintHandlersWrap(() => {
return realRunInThisContext.call(this, options);
});
} else {
return realRunInThisContext.call(this, options);
}
};
Script.prototype.runInContext = function(contextifiedSandbox, options) {
if (options && options.breakOnSigint) {
return sigintHandlersWrap(() => {
return realRunInContext.call(this, contextifiedSandbox, options);
});
} else {
return realRunInContext.call(this, contextifiedSandbox, options);
}
};
Script.prototype.runInNewContext = function(sandbox, options) {
var context = exports.createContext(sandbox);
return this.runInContext(context, options);
@ -55,3 +78,27 @@ exports.runInThisContext = function(code, options) {
};
exports.isContext = binding.isContext;
// Remove all SIGINT listeners and re-attach them after the wrapped function
// has executed, so that caught SIGINT are handled by the listeners again.
function sigintHandlersWrap(fn) {
// Using the internal list here to make sure `.once()` wrappers are used,
// not the original ones.
let sigintListeners = process._events.SIGINT;
if (!Array.isArray(sigintListeners))
sigintListeners = sigintListeners ? [sigintListeners] : [];
else
sigintListeners = sigintListeners.slice();
process.removeAllListeners('SIGINT');
try {
return fn();
} finally {
// Add using the public methods so that the `newListener` handler of
// process can re-attach the listeners.
for (const listener of sigintListeners) {
process.addListener('SIGINT', listener);
}
}
}

8
src/node.cc

@ -3317,7 +3317,7 @@ static void AtExit() {
}
static void SignalExit(int signo) {
void SignalExit(int signo) {
uv_tty_reset_mode();
#ifdef __FreeBSD__
// FreeBSD has a nasty bug, see RegisterSignalHandler for details
@ -3819,9 +3819,9 @@ static void EnableDebugSignalHandler(int signo) {
}
static void RegisterSignalHandler(int signal,
void (*handler)(int signal),
bool reset_handler = false) {
void RegisterSignalHandler(int signal,
void (*handler)(int signal),
bool reset_handler) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handler;

42
src/node_contextify.cc

@ -553,6 +553,7 @@ class ContextifyScript : public BaseObject {
TryCatch try_catch(args.GetIsolate());
uint64_t timeout = GetTimeoutArg(args, 0);
bool display_errors = GetDisplayErrorsArg(args, 0);
bool break_on_sigint = GetBreakOnSigintArg(args, 0);
if (try_catch.HasCaught()) {
try_catch.ReThrow();
return;
@ -560,7 +561,7 @@ class ContextifyScript : public BaseObject {
// Do the eval within this context
Environment* env = Environment::GetCurrent(args);
EvalMachine(env, timeout, display_errors, args, try_catch);
EvalMachine(env, timeout, display_errors, break_on_sigint, args, try_catch);
}
// args: sandbox, [options]
@ -569,6 +570,7 @@ class ContextifyScript : public BaseObject {
int64_t timeout;
bool display_errors;
bool break_on_sigint;
// Assemble arguments
if (!args[0]->IsObject()) {
@ -581,6 +583,7 @@ class ContextifyScript : public BaseObject {
TryCatch try_catch(env->isolate());
timeout = GetTimeoutArg(args, 1);
display_errors = GetDisplayErrorsArg(args, 1);
break_on_sigint = GetBreakOnSigintArg(args, 1);
if (try_catch.HasCaught()) {
try_catch.ReThrow();
return;
@ -605,6 +608,7 @@ class ContextifyScript : public BaseObject {
if (EvalMachine(contextify_context->env(),
timeout,
display_errors,
break_on_sigint,
args,
try_catch)) {
contextify_context->CopyProperties();
@ -653,6 +657,23 @@ class ContextifyScript : public BaseObject {
True(env->isolate()));
}
static bool GetBreakOnSigintArg(const FunctionCallbackInfo<Value>& args,
const int i) {
if (args[i]->IsUndefined() || args[i]->IsString()) {
return false;
}
if (!args[i]->IsObject()) {
Environment::ThrowTypeError(args.GetIsolate(),
"options must be an object");
return false;
}
Local<String> key = FIXED_ONE_BYTE_STRING(args.GetIsolate(),
"breakOnSigint");
Local<Value> value = args[i].As<Object>()->Get(key);
return value->IsTrue();
}
static int64_t GetTimeoutArg(const FunctionCallbackInfo<Value>& args,
const int i) {
if (args[i]->IsUndefined() || args[i]->IsString()) {
@ -798,6 +819,7 @@ class ContextifyScript : public BaseObject {
static bool EvalMachine(Environment* env,
const int64_t timeout,
const bool display_errors,
const bool break_on_sigint,
const FunctionCallbackInfo<Value>& args,
TryCatch& try_catch) {
if (!ContextifyScript::InstanceOf(env, args.Holder())) {
@ -813,16 +835,30 @@ class ContextifyScript : public BaseObject {
Local<Script> script = unbound_script->BindToCurrentContext();
Local<Value> result;
if (timeout != -1) {
bool timed_out = false;
if (break_on_sigint && timeout != -1) {
Watchdog wd(env->isolate(), timeout);
SigintWatchdog swd(env->isolate());
result = script->Run();
timed_out = wd.HasTimedOut();
} else if (break_on_sigint) {
SigintWatchdog swd(env->isolate());
result = script->Run();
} else if (timeout != -1) {
Watchdog wd(env->isolate(), timeout);
result = script->Run();
timed_out = wd.HasTimedOut();
} else {
result = script->Run();
}
if (try_catch.HasCaught() && try_catch.HasTerminated()) {
env->isolate()->CancelTerminateExecution();
env->ThrowError("Script execution timed out.");
if (timed_out) {
env->ThrowError("Script execution timed out.");
} else {
env->ThrowError("Script execution interrupted.");
}
try_catch.ReThrow();
return false;
}

7
src/node_internals.h

@ -99,6 +99,13 @@ void GetSockOrPeerName(const v8::FunctionCallbackInfo<v8::Value>& args) {
args.GetReturnValue().Set(err);
}
void SignalExit(int signo);
#ifdef __POSIX__
void RegisterSignalHandler(int signal,
void (*handler)(int signal),
bool reset_handler = false);
#endif
#ifdef _WIN32
// emulate snprintf() on windows, _snprintf() doesn't zero-terminate the buffer
// on overflow...

18
src/node_util.cc

@ -1,4 +1,5 @@
#include "node.h"
#include "node_watchdog.h"
#include "v8.h"
#include "env.h"
#include "env-inl.h"
@ -88,6 +89,20 @@ static void SetHiddenValue(const FunctionCallbackInfo<Value>& args) {
}
void StartSigintWatchdog(const FunctionCallbackInfo<Value>& args) {
int ret = SigintWatchdogHelper::GetInstance()->Start();
if (ret != 0) {
Environment* env = Environment::GetCurrent(args);
env->ThrowErrnoException(ret, "StartSigintWatchdog");
}
}
void StopSigintWatchdog(const FunctionCallbackInfo<Value>& args) {
bool had_pending_signals = SigintWatchdogHelper::GetInstance()->Stop();
args.GetReturnValue().Set(had_pending_signals);
}
void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context) {
@ -100,6 +115,9 @@ void Initialize(Local<Object> target,
env->SetMethod(target, "getHiddenValue", GetHiddenValue);
env->SetMethod(target, "setHiddenValue", SetHiddenValue);
env->SetMethod(target, "getProxyDetails", GetProxyDetails);
env->SetMethod(target, "startSigintWatchdog", StartSigintWatchdog);
env->SetMethod(target, "stopSigintWatchdog", StopSigintWatchdog);
}
} // namespace util

225
src/node_watchdog.cc

@ -1,10 +1,13 @@
#include "node_watchdog.h"
#include "node_internals.h"
#include "util.h"
#include "util-inl.h"
#include <algorithm>
namespace node {
Watchdog::Watchdog(v8::Isolate* isolate, uint64_t ms) : isolate_(isolate),
timed_out_(false),
destroyed_(false) {
int rc;
loop_ = new uv_loop_t;
@ -79,9 +82,231 @@ void Watchdog::Async(uv_async_t* async) {
void Watchdog::Timer(uv_timer_t* timer) {
Watchdog* w = ContainerOf(&Watchdog::timer_, timer);
w->timed_out_ = true;
uv_stop(w->loop_);
w->isolate()->TerminateExecution();
}
SigintWatchdog::~SigintWatchdog() {
Destroy();
}
void SigintWatchdog::Dispose() {
Destroy();
}
SigintWatchdog::SigintWatchdog(v8::Isolate* isolate)
: isolate_(isolate), destroyed_(false) {
// Register this watchdog with the global SIGINT/Ctrl+C listener.
SigintWatchdogHelper::GetInstance()->Register(this);
// Start the helper thread, if that has not already happened.
SigintWatchdogHelper::GetInstance()->Start();
}
void SigintWatchdog::Destroy() {
if (destroyed_) {
return;
}
destroyed_ = true;
SigintWatchdogHelper::GetInstance()->Unregister(this);
SigintWatchdogHelper::GetInstance()->Stop();
}
void SigintWatchdog::HandleSigint() {
isolate_->TerminateExecution();
}
#ifdef __POSIX__
void* SigintWatchdogHelper::RunSigintWatchdog(void* arg) {
// Inside the helper thread.
bool is_stopping;
do {
uv_sem_wait(&instance.sem_);
is_stopping = InformWatchdogsAboutSignal();
} while (!is_stopping);
return nullptr;
}
void SigintWatchdogHelper::HandleSignal(int signum) {
uv_sem_post(&instance.sem_);
}
#else
// Windows starts a separate thread for executing the handler, so no extra
// helper thread is required.
BOOL WINAPI SigintWatchdogHelper::WinCtrlCHandlerRoutine(DWORD dwCtrlType) {
if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT) {
InformWatchdogsAboutSignal();
// Return true because the signal has been handled.
return TRUE;
} else {
return FALSE;
}
}
#endif
bool SigintWatchdogHelper::InformWatchdogsAboutSignal() {
uv_mutex_lock(&instance.list_mutex_);
bool is_stopping = false;
#ifdef __POSIX__
is_stopping = instance.stopping_;
#endif
// If there are no listeners and the helper thread has been awoken by a signal
// (= not when stopping it), indicate that by setting has_pending_signal_.
if (instance.watchdogs_.empty() && !is_stopping) {
instance.has_pending_signal_ = true;
}
for (auto it : instance.watchdogs_)
it->HandleSigint();
uv_mutex_unlock(&instance.list_mutex_);
return is_stopping;
}
int SigintWatchdogHelper::Start() {
int ret = 0;
uv_mutex_lock(&mutex_);
if (start_stop_count_++ > 0) {
goto dont_start;
}
#ifdef __POSIX__
CHECK_EQ(has_running_thread_, false);
has_pending_signal_ = false;
stopping_ = false;
sigset_t sigmask;
sigfillset(&sigmask);
CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, &sigmask));
ret = pthread_create(&thread_, nullptr, RunSigintWatchdog, nullptr);
CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, nullptr));
if (ret != 0) {
goto dont_start;
}
has_running_thread_ = true;
RegisterSignalHandler(SIGINT, HandleSignal);
#else
SetConsoleCtrlHandler(WinCtrlCHandlerRoutine, TRUE);
#endif
dont_start:
uv_mutex_unlock(&mutex_);
return ret;
}
bool SigintWatchdogHelper::Stop() {
uv_mutex_lock(&mutex_);
uv_mutex_lock(&list_mutex_);
bool had_pending_signal = has_pending_signal_;
if (--start_stop_count_ > 0) {
uv_mutex_unlock(&list_mutex_);
goto dont_stop;
}
#ifdef __POSIX__
// Set stopping now because it's only protected by list_mutex_.
stopping_ = true;
#endif
watchdogs_.clear();
uv_mutex_unlock(&list_mutex_);
#ifdef __POSIX__
if (!has_running_thread_) {
goto dont_stop;
}
// Wake up the helper thread.
uv_sem_post(&sem_);
// Wait for the helper thread to finish.
CHECK_EQ(0, pthread_join(thread_, nullptr));
has_running_thread_ = false;
RegisterSignalHandler(SIGINT, SignalExit, true);
#else
SetConsoleCtrlHandler(WinCtrlCHandlerRoutine, FALSE);
#endif
had_pending_signal = has_pending_signal_;
dont_stop:
uv_mutex_unlock(&mutex_);
has_pending_signal_ = false;
return had_pending_signal;
}
void SigintWatchdogHelper::Register(SigintWatchdog* wd) {
uv_mutex_lock(&list_mutex_);
watchdogs_.push_back(wd);
uv_mutex_unlock(&list_mutex_);
}
void SigintWatchdogHelper::Unregister(SigintWatchdog* wd) {
uv_mutex_lock(&list_mutex_);
auto it = std::find(watchdogs_.begin(), watchdogs_.end(), wd);
CHECK_NE(it, watchdogs_.end());
watchdogs_.erase(it);
uv_mutex_unlock(&list_mutex_);
}
SigintWatchdogHelper::SigintWatchdogHelper()
: start_stop_count_(0),
has_pending_signal_(false) {
#ifdef __POSIX__
has_running_thread_ = false;
stopping_ = false;
CHECK_EQ(0, uv_sem_init(&sem_, 0));
#endif
CHECK_EQ(0, uv_mutex_init(&mutex_));
CHECK_EQ(0, uv_mutex_init(&list_mutex_));
};
SigintWatchdogHelper::~SigintWatchdogHelper() {
start_stop_count_ = 0;
Stop();
#ifdef __POSIX__
CHECK_EQ(has_running_thread_, false);
uv_sem_destroy(&sem_);
#endif
uv_mutex_destroy(&mutex_);
uv_mutex_destroy(&list_mutex_);
}
SigintWatchdogHelper SigintWatchdogHelper::instance;
} // namespace node

59
src/node_watchdog.h

@ -5,6 +5,11 @@
#include "v8.h"
#include "uv.h"
#include <vector>
#ifdef __POSIX__
#include <pthread.h>
#endif
namespace node {
@ -16,7 +21,7 @@ class Watchdog {
void Dispose();
v8::Isolate* isolate() { return isolate_; }
bool HasTimedOut() { return timed_out_; }
private:
void Destroy();
@ -29,9 +34,61 @@ class Watchdog {
uv_loop_t* loop_;
uv_async_t async_;
uv_timer_t timer_;
bool timed_out_;
bool destroyed_;
};
class SigintWatchdog {
public:
explicit SigintWatchdog(v8::Isolate* isolate);
~SigintWatchdog();
void Dispose();
v8::Isolate* isolate() { return isolate_; }
void HandleSigint();
private:
void Destroy();
v8::Isolate* isolate_;
bool destroyed_;
};
class SigintWatchdogHelper {
public:
static SigintWatchdogHelper* GetInstance() { return &instance; }
void Register(SigintWatchdog* watchdog);
void Unregister(SigintWatchdog* watchdog);
int Start();
bool Stop();
private:
SigintWatchdogHelper();
~SigintWatchdogHelper();
static bool InformWatchdogsAboutSignal();
static SigintWatchdogHelper instance;
int start_stop_count_;
uv_mutex_t mutex_;
uv_mutex_t list_mutex_;
std::vector<SigintWatchdog*> watchdogs_;
bool has_pending_signal_;
#ifdef __POSIX__
pthread_t thread_;
uv_sem_t sem_;
bool has_running_thread_;
bool stopping_;
static void* RunSigintWatchdog(void* arg);
static void HandleSignal(int signum);
#else
static BOOL WINAPI WinCtrlCHandlerRoutine(DWORD dwCtrlType);
#endif
};
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

3
test/message/eval_messages.out

@ -16,6 +16,7 @@ throw new Error("hello")
^
Error: hello
at [eval]:1:7
at ContextifyScript.Script.runInThisContext (vm.js:*)
at Object.exports.runInThisContext (vm.js:*)
at Object.<anonymous> ([eval]-wrapper:*:*)
at Module._compile (module.js:*:*)
@ -27,6 +28,7 @@ throw new Error("hello")
^
Error: hello
at [eval]:1:7
at ContextifyScript.Script.runInThisContext (vm.js:*)
at Object.exports.runInThisContext (vm.js:*)
at Object.<anonymous> ([eval]-wrapper:*:*)
at Module._compile (module.js:*:*)
@ -39,6 +41,7 @@ var x = 100; y = x;
^
ReferenceError: y is not defined
at [eval]:1:16
at ContextifyScript.Script.runInThisContext (vm.js:*)
at Object.exports.runInThisContext (vm.js:*)
at Object.<anonymous> ([eval]-wrapper:*:*)
at Module._compile (module.js:*:*)

3
test/message/stdin_messages.out

@ -18,6 +18,7 @@ throw new Error("hello")
^
Error: hello
at [stdin]:1:*
at ContextifyScript.Script.runInThisContext (vm.js:*)
at Object.exports.runInThisContext (vm.js:*)
at Object.<anonymous> ([stdin]-wrapper:*:*)
at Module._compile (module.js:*:*)
@ -30,6 +31,7 @@ throw new Error("hello")
^
Error: hello
at [stdin]:1:*
at ContextifyScript.Script.runInThisContext (vm.js:*)
at Object.exports.runInThisContext (vm.js:*)
at Object.<anonymous> ([stdin]-wrapper:*:*)
at Module._compile (module.js:*:*)
@ -43,6 +45,7 @@ var x = 100; y = x;
^
ReferenceError: y is not defined
at [stdin]:1:16
at ContextifyScript.Script.runInThisContext (vm.js:*)
at Object.exports.runInThisContext (vm.js:*)
at Object.<anonymous> ([stdin]-wrapper:*:*)
at Module._compile (module.js:*:*)

2
test/message/undefined_reference_in_new_context.out

@ -5,6 +5,7 @@ foo.bar = 5;
^
ReferenceError: foo is not defined
at evalmachine.<anonymous>:1:1
at ContextifyScript.Script.runInContext (vm.js:*)
at ContextifyScript.Script.runInNewContext (vm.js:*)
at Object.exports.runInNewContext (vm.js:*)
at Object.<anonymous> (*test*message*undefined_reference_in_new_context.js:*)
@ -13,4 +14,3 @@ ReferenceError: foo is not defined
at Module.load (module.js:*)
at tryModuleLoad (module.js:*:*)
at Function.Module._load (module.js:*:*)
at Module.runMain (module.js:*:*)

2
test/message/vm_display_runtime_error.out

@ -5,6 +5,7 @@ throw new Error("boo!")
^
Error: boo!
at test.vm:1:7
at ContextifyScript.Script.runInThisContext (vm.js:*)
at Object.exports.runInThisContext (vm.js:*)
at Object.<anonymous> (*test*message*vm_display_runtime_error.js:*)
at Module._compile (module.js:*)
@ -13,4 +14,3 @@ Error: boo!
at tryModuleLoad (module.js:*:*)
at Function.Module._load (module.js:*)
at Module.runMain (module.js:*)
at run (bootstrap_node.js:*)

2
test/message/vm_dont_display_runtime_error.out

@ -5,6 +5,7 @@ throw new Error("boo!")
^
Error: boo!
at test.vm:1:7
at ContextifyScript.Script.runInThisContext (vm.js:*)
at Object.exports.runInThisContext (vm.js:*)
at Object.<anonymous> (*test*message*vm_dont_display_runtime_error.js:*)
at Module._compile (module.js:*)
@ -13,4 +14,3 @@ Error: boo!
at tryModuleLoad (module.js:*:*)
at Function.Module._load (module.js:*)
at Module.runMain (module.js:*)
at run (bootstrap_node.js:*)

53
test/parallel/test-util-sigint-watchdog.js

@ -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))();

77
test/parallel/test-vm-sigint-existing-handler.js

@ -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);
});

39
test/parallel/test-vm-sigint.js

@ -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…
Cancel
Save