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>
This commit is contained in:
parent
de9a84186e
commit
0815b9401d
@ -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
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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...
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:*:*)
|
||||
|
@ -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:*:*)
|
||||
|
@ -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:*:*)
|
||||
|
@ -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:*)
|
||||
|
@ -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
Normal file
53
test/parallel/test-util-sigint-watchdog.js
Normal file
@ -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
Normal file
77
test/parallel/test-vm-sigint-existing-handler.js
Normal file
@ -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
Normal file
39
test/parallel/test-vm-sigint.js
Normal file
@ -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…
x
Reference in New Issue
Block a user