diff options
-rw-r--r-- | doc/api/vm.md | 6 | ||||
-rw-r--r-- | lib/vm.js | 47 | ||||
-rw-r--r-- | src/node.cc | 8 | ||||
-rw-r--r-- | src/node_contextify.cc | 42 | ||||
-rw-r--r-- | src/node_internals.h | 7 | ||||
-rw-r--r-- | src/node_util.cc | 18 | ||||
-rw-r--r-- | src/node_watchdog.cc | 225 | ||||
-rw-r--r-- | src/node_watchdog.h | 59 | ||||
-rw-r--r-- | test/message/eval_messages.out | 3 | ||||
-rw-r--r-- | test/message/stdin_messages.out | 3 | ||||
-rw-r--r-- | test/message/undefined_reference_in_new_context.out | 2 | ||||
-rw-r--r-- | test/message/vm_display_runtime_error.out | 2 | ||||
-rw-r--r-- | test/message/vm_dont_display_runtime_error.out | 2 | ||||
-rw-r--r-- | test/parallel/test-util-sigint-watchdog.js | 53 | ||||
-rw-r--r-- | test/parallel/test-vm-sigint-existing-handler.js | 77 | ||||
-rw-r--r-- | test/parallel/test-vm-sigint.js | 39 |
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 @@ -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); +}); |