diff options
author | legendecas <legendecas@gmail.com> | 2019-08-19 16:03:08 +0300 |
---|---|---|
committer | legendecas <legendecas@gmail.com> | 2020-01-28 08:52:27 +0300 |
commit | 7b7e7bd185aa606cf2845747dd3844a9b12ec9ab (patch) | |
tree | 5081e7a080edf45d2b5f82039e0d014c0ab46d96 /src/node_watchdog.cc | |
parent | 78743f8e3912fa04670ceab5365f081b3079507b (diff) |
src,lib: make ^C print a JS stack trace
If terminating the process with ctrl-c / SIGINT, prints a JS stacktrace
leading up to the currently executing code.
The feature would be enabled under option `--trace-sigint`.
Conditions of no stacktrace on sigint:
- has (an) active sigint listener(s);
- main thread is idle (i.e. uv polling), a message instead of stacktrace
would be printed.
PR-URL: https://github.com/nodejs/node/pull/29207
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Christopher Hiller <boneskull@boneskull.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Diffstat (limited to 'src/node_watchdog.cc')
-rw-r--r-- | src/node_watchdog.cc | 144 |
1 files changed, 136 insertions, 8 deletions
diff --git a/src/node_watchdog.cc b/src/node_watchdog.cc index 4ab625ce65b..4cc75c31604 100644 --- a/src/node_watchdog.cc +++ b/src/node_watchdog.cc @@ -21,6 +21,7 @@ #include <algorithm> +#include "async_wrap-inl.h" #include "debug_utils-inl.h" #include "env-inl.h" #include "node_errors.h" @@ -30,6 +31,12 @@ namespace node { +using v8::Context; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Object; +using v8::Value; + Watchdog::Watchdog(v8::Isolate* isolate, uint64_t ms, bool* timed_out) : isolate_(isolate), timed_out_(timed_out) { @@ -106,10 +113,116 @@ SigintWatchdog::~SigintWatchdog() { SigintWatchdogHelper::GetInstance()->Stop(); } - -void SigintWatchdog::HandleSigint() { +SignalPropagation SigintWatchdog::HandleSigint() { *received_signal_ = true; isolate_->TerminateExecution(); + return SignalPropagation::kStopPropagation; +} + +void TraceSigintWatchdog::Init(Environment* env, Local<Object> target) { + Local<FunctionTemplate> constructor = env->NewFunctionTemplate(New); + constructor->InstanceTemplate()->SetInternalFieldCount(1); + Local<v8::String> js_sigint_watch_dog = + FIXED_ONE_BYTE_STRING(env->isolate(), "TraceSigintWatchdog"); + constructor->SetClassName(js_sigint_watch_dog); + constructor->Inherit(HandleWrap::GetConstructorTemplate(env)); + + env->SetProtoMethod(constructor, "start", Start); + env->SetProtoMethod(constructor, "stop", Stop); + + target + ->Set(env->context(), + js_sigint_watch_dog, + constructor->GetFunction(env->context()).ToLocalChecked()) + .Check(); +} + +void TraceSigintWatchdog::New(const FunctionCallbackInfo<Value>& args) { + // This constructor should not be exposed to public javascript. + // Therefore we assert that we are not trying to call this as a + // normal function. + CHECK(args.IsConstructCall()); + Environment* env = Environment::GetCurrent(args); + new TraceSigintWatchdog(env, args.This()); +} + +void TraceSigintWatchdog::Start(const FunctionCallbackInfo<Value>& args) { + TraceSigintWatchdog* watchdog; + ASSIGN_OR_RETURN_UNWRAP(&watchdog, args.Holder()); + // Register this watchdog with the global SIGINT/Ctrl+C listener. + SigintWatchdogHelper::GetInstance()->Register(watchdog); + // Start the helper thread, if that has not already happened. + int r = SigintWatchdogHelper::GetInstance()->Start(); + CHECK_EQ(r, 0); +} + +void TraceSigintWatchdog::Stop(const FunctionCallbackInfo<Value>& args) { + TraceSigintWatchdog* watchdog; + ASSIGN_OR_RETURN_UNWRAP(&watchdog, args.Holder()); + SigintWatchdogHelper::GetInstance()->Unregister(watchdog); + SigintWatchdogHelper::GetInstance()->Stop(); +} + +TraceSigintWatchdog::TraceSigintWatchdog(Environment* env, Local<Object> object) + : HandleWrap(env, + object, + reinterpret_cast<uv_handle_t*>(&handle_), + AsyncWrap::PROVIDER_SIGINTWATCHDOG) { + int r = uv_async_init(env->event_loop(), &handle_, [](uv_async_t* handle) { + TraceSigintWatchdog* watchdog = + ContainerOf(&TraceSigintWatchdog::handle_, handle); + watchdog->signal_flag_ = SignalFlags::FromIdle; + watchdog->HandleInterrupt(); + }); + CHECK_EQ(r, 0); + uv_unref(reinterpret_cast<uv_handle_t*>(&handle_)); +} + +SignalPropagation TraceSigintWatchdog::HandleSigint() { + /** + * In case of uv loop polling, i.e. no JS currently running, activate the + * loop to run a piece of JS code to trigger interruption. + */ + CHECK_EQ(uv_async_send(&handle_), 0); + env()->isolate()->RequestInterrupt( + [](v8::Isolate* isolate, void* data) { + TraceSigintWatchdog* self = static_cast<TraceSigintWatchdog*>(data); + if (self->signal_flag_ == SignalFlags::None) { + self->signal_flag_ = SignalFlags::FromInterrupt; + } + self->HandleInterrupt(); + }, + this); + return SignalPropagation::kContinuePropagation; +} + +void TraceSigintWatchdog::HandleInterrupt() { + // Do not nest interrupts. + if (interrupting) { + return; + } + interrupting = true; + if (signal_flag_ == SignalFlags::None) { + return; + } + Environment* env_ = env(); + // FIXME: Before + // https://github.com/nodejs/node/pull/29207#issuecomment-527667993 get + // fixed, additional JavaScript code evaluation shall be prevented from + // running during interruption. + FPrintF(stderr, + "KEYBOARD_INTERRUPT: Script execution was interrupted by `SIGINT`\n"); + if (signal_flag_ == SignalFlags::FromInterrupt) { + PrintStackTrace(env_->isolate(), + v8::StackTrace::CurrentStackTrace( + env_->isolate(), 10, v8::StackTrace::kDetailed)); + } + signal_flag_ = SignalFlags::None; + interrupting = false; + + SigintWatchdogHelper::GetInstance()->Unregister(this); + SigintWatchdogHelper::GetInstance()->Stop(); + raise(SIGINT); } #ifdef __POSIX__ @@ -163,8 +276,13 @@ bool SigintWatchdogHelper::InformWatchdogsAboutSignal() { instance.has_pending_signal_ = true; } - for (auto it : instance.watchdogs_) - it->HandleSigint(); + for (auto it = instance.watchdogs_.rbegin(); it != instance.watchdogs_.rend(); + it++) { + SignalPropagation wp = (*it)->HandleSigint(); + if (wp == SignalPropagation::kStopPropagation) { + break; + } + } return is_stopping; } @@ -260,15 +378,13 @@ bool SigintWatchdogHelper::HasPendingSignal() { return has_pending_signal_; } - -void SigintWatchdogHelper::Register(SigintWatchdog* wd) { +void SigintWatchdogHelper::Register(SigintWatchdogBase* wd) { Mutex::ScopedLock lock(list_mutex_); watchdogs_.push_back(wd); } - -void SigintWatchdogHelper::Unregister(SigintWatchdog* wd) { +void SigintWatchdogHelper::Unregister(SigintWatchdogBase* wd) { Mutex::ScopedLock lock(list_mutex_); auto it = std::find(watchdogs_.begin(), watchdogs_.end(), wd); @@ -303,4 +419,16 @@ SigintWatchdogHelper::~SigintWatchdogHelper() { SigintWatchdogHelper SigintWatchdogHelper::instance; +namespace watchdog { +static void Initialize(Local<Object> target, + Local<Value> unused, + Local<Context> context, + void* priv) { + Environment* env = Environment::GetCurrent(context); + TraceSigintWatchdog::Init(env, target); +} +} // namespace watchdog + } // namespace node + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(watchdog, node::watchdog::Initialize); |