summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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);
+});