summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnna Henningsen <anna@addaleax.net>2016-05-08 03:28:47 +0200
committerAnna Henningsen <anna@addaleax.net>2016-06-18 20:44:16 +0200
commit0815b9401d087202cd64458b6906a5225929fc5d (patch)
tree78fb84a7b6887091cd392e5d9feff11bae6edf96
parentde9a84186e6da9e4e6ee9434aa89715bf3eb9172 (diff)
downloadandroid-node-v8-0815b9401d087202cd64458b6906a5225929fc5d.tar.gz
android-node-v8-0815b9401d087202cd64458b6906a5225929fc5d.tar.bz2
android-node-v8-0815b9401d087202cd64458b6906a5225929fc5d.zip
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>
-rw-r--r--doc/api/vm.md6
-rw-r--r--lib/vm.js47
-rw-r--r--src/node.cc8
-rw-r--r--src/node_contextify.cc42
-rw-r--r--src/node_internals.h7
-rw-r--r--src/node_util.cc18
-rw-r--r--src/node_watchdog.cc225
-rw-r--r--src/node_watchdog.h59
-rw-r--r--test/message/eval_messages.out3
-rw-r--r--test/message/stdin_messages.out3
-rw-r--r--test/message/undefined_reference_in_new_context.out2
-rw-r--r--test/message/vm_display_runtime_error.out2
-rw-r--r--test/message/vm_dont_display_runtime_error.out2
-rw-r--r--test/parallel/test-util-sigint-watchdog.js53
-rw-r--r--test/parallel/test-vm-sigint-existing-handler.js77
-rw-r--r--test/parallel/test-vm-sigint.js39
16 files changed, 582 insertions, 11 deletions
diff --git a/doc/api/vm.md b/doc/api/vm.md
index b65826b2d3..0141ad605e 100644
--- a/doc/api/vm.md
+++ b/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
diff --git a/lib/vm.js b/lib/vm.js
index b4a2b99990..364a37eacb 100644
--- a/lib/vm.js
+++ b/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);
+ }
+ }
+}
diff --git a/src/node.cc b/src/node.cc
index e511f900a7..3886be6de6 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -3239,7 +3239,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
@@ -3735,9 +3735,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;
diff --git a/src/node_contextify.cc b/src/node_contextify.cc
index 774871b852..a4a7693594 100644
--- a/src/node_contextify.cc
+++ b/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;
}
diff --git a/src/node_internals.h b/src/node_internals.h
index 0865d71a9b..b92c19734f 100644
--- a/src/node_internals.h
+++ b/src/node_internals.h
@@ -92,6 +92,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...
diff --git a/src/node_util.cc b/src/node_util.cc
index 5089188d50..e60af80326 100644
--- a/src/node_util.cc
+++ b/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"
@@ -98,6 +99,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) {
@@ -120,6 +135,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
diff --git a/src/node_watchdog.cc b/src/node_watchdog.cc
index dfa7debd9a..8e94bc8d9d 100644
--- a/src/node_watchdog.cc
+++ b/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
diff --git a/src/node_watchdog.h b/src/node_watchdog.h
index 36125ac0e1..65815e2bcd 100644
--- a/src/node_watchdog.h
+++ b/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
diff --git a/test/message/eval_messages.out b/test/message/eval_messages.out
index f1b9273342..7fbf51f7e0 100644
--- a/test/message/eval_messages.out
+++ b/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:*:*)
diff --git a/test/message/stdin_messages.out b/test/message/stdin_messages.out
index 1d860918df..62c43b02a8 100644
--- a/test/message/stdin_messages.out
+++ b/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:*:*)
diff --git a/test/message/undefined_reference_in_new_context.out b/test/message/undefined_reference_in_new_context.out
index d209eb215c..04b9a21acd 100644
--- a/test/message/undefined_reference_in_new_context.out
+++ b/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:*:*)
diff --git a/test/message/vm_display_runtime_error.out b/test/message/vm_display_runtime_error.out
index 9c14783472..b7fe798cd2 100644
--- a/test/message/vm_display_runtime_error.out
+++ b/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:*)
diff --git a/test/message/vm_dont_display_runtime_error.out b/test/message/vm_dont_display_runtime_error.out
index 6cc464505f..e0795d1f53 100644
--- a/test/message/vm_dont_display_runtime_error.out
+++ b/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:*)
diff --git a/test/parallel/test-util-sigint-watchdog.js b/test/parallel/test-util-sigint-watchdog.js
new file mode 100644
index 0000000000..2f95286a5e
--- /dev/null
+++ b/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))();
diff --git a/test/parallel/test-vm-sigint-existing-handler.js b/test/parallel/test-vm-sigint-existing-handler.js
new file mode 100644
index 0000000000..e86bbeec0b
--- /dev/null
+++ b/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);
+});
diff --git a/test/parallel/test-vm-sigint.js b/test/parallel/test-vm-sigint.js
new file mode 100644
index 0000000000..5260a8464f
--- /dev/null
+++ b/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);
+});