| // Copyright Joyent, Inc. and other Node contributors. |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a |
| // copy of this software and associated documentation files (the |
| // "Software"), to deal in the Software without restriction, including |
| // without limitation the rights to use, copy, modify, merge, publish, |
| // distribute, sublicense, and/or sell copies of the Software, and to permit |
| // persons to whom the Software is furnished to do so, subject to the |
| // following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included |
| // in all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN |
| // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
| // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| // USE OR OTHER DEALINGS IN THE SOFTWARE. |
| |
| #include <algorithm> |
| |
| #include "async_wrap-inl.h" |
| #include "debug_utils-inl.h" |
| #include "env-inl.h" |
| #include "node_errors.h" |
| #include "node_internals.h" |
| #include "node_watchdog.h" |
| #include "util-inl.h" |
| |
| namespace node { |
| |
| using v8::Context; |
| using v8::FunctionCallbackInfo; |
| using v8::FunctionTemplate; |
| using v8::Local; |
| using v8::Object; |
| using v8::Value; |
| |
| Watchdog::Watchdog(v8::Isolate* isolate, uint64_t ms, bool* timed_out) |
| : isolate_(isolate), timed_out_(timed_out) { |
| |
| int rc; |
| rc = uv_loop_init(&loop_); |
| if (rc != 0) { |
| FatalError("node::Watchdog::Watchdog()", |
| "Failed to initialize uv loop."); |
| } |
| |
| rc = uv_async_init(&loop_, &async_, [](uv_async_t* signal) { |
| Watchdog* w = ContainerOf(&Watchdog::async_, signal); |
| uv_stop(&w->loop_); |
| }); |
| |
| CHECK_EQ(0, rc); |
| |
| rc = uv_timer_init(&loop_, &timer_); |
| CHECK_EQ(0, rc); |
| |
| rc = uv_timer_start(&timer_, &Watchdog::Timer, ms, 0); |
| CHECK_EQ(0, rc); |
| |
| rc = uv_thread_create(&thread_, &Watchdog::Run, this); |
| CHECK_EQ(0, rc); |
| } |
| |
| |
| Watchdog::~Watchdog() { |
| uv_async_send(&async_); |
| uv_thread_join(&thread_); |
| |
| uv_close(reinterpret_cast<uv_handle_t*>(&async_), nullptr); |
| |
| // UV_RUN_DEFAULT so that libuv has a chance to clean up. |
| uv_run(&loop_, UV_RUN_DEFAULT); |
| |
| CheckedUvLoopClose(&loop_); |
| } |
| |
| |
| void Watchdog::Run(void* arg) { |
| Watchdog* wd = static_cast<Watchdog*>(arg); |
| |
| // UV_RUN_DEFAULT the loop will be stopped either by the async or the |
| // timer handle. |
| uv_run(&wd->loop_, UV_RUN_DEFAULT); |
| |
| // Loop ref count reaches zero when both handles are closed. |
| // Close the timer handle on this side and let ~Watchdog() close async_ |
| uv_close(reinterpret_cast<uv_handle_t*>(&wd->timer_), nullptr); |
| } |
| |
| void Watchdog::Timer(uv_timer_t* timer) { |
| Watchdog* w = ContainerOf(&Watchdog::timer_, timer); |
| *w->timed_out_ = true; |
| w->isolate()->TerminateExecution(); |
| uv_stop(&w->loop_); |
| } |
| |
| |
| SigintWatchdog::SigintWatchdog( |
| v8::Isolate* isolate, bool* received_signal) |
| : isolate_(isolate), received_signal_(received_signal) { |
| // 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(); |
| } |
| |
| |
| SigintWatchdog::~SigintWatchdog() { |
| SigintWatchdogHelper::GetInstance()->Unregister(this); |
| SigintWatchdogHelper::GetInstance()->Stop(); |
| } |
| |
| 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( |
| TraceSigintWatchdog::kInternalFieldCount); |
| 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__ |
| 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, |
| siginfo_t* info, |
| void* ucontext) { |
| 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 (!instance.watchdog_disabled_ && |
| (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() { |
| Mutex::ScopedLock list_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_.rbegin(); it != instance.watchdogs_.rend(); |
| it++) { |
| SignalPropagation wp = (*it)->HandleSigint(); |
| if (wp == SignalPropagation::kStopPropagation) { |
| break; |
| } |
| } |
| |
| return is_stopping; |
| } |
| |
| |
| int SigintWatchdogHelper::Start() { |
| Mutex::ScopedLock lock(mutex_); |
| |
| if (start_stop_count_++ > 0) { |
| return 0; |
| } |
| |
| #ifndef __Fuchsia__ |
| #ifdef __POSIX__ |
| CHECK_EQ(has_running_thread_, false); |
| has_pending_signal_ = false; |
| stopping_ = false; |
| |
| sigset_t sigmask; |
| sigfillset(&sigmask); |
| sigset_t savemask; |
| CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, &savemask)); |
| sigmask = savemask; |
| int ret = pthread_create(&thread_, nullptr, RunSigintWatchdog, nullptr); |
| CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, nullptr)); |
| if (ret != 0) { |
| return ret; |
| } |
| has_running_thread_ = true; |
| |
| RegisterSignalHandler(SIGINT, HandleSignal); |
| #else |
| if (watchdog_disabled_) { |
| watchdog_disabled_ = false; |
| } else { |
| SetConsoleCtrlHandler(WinCtrlCHandlerRoutine, TRUE); |
| } |
| #endif |
| #endif |
| |
| return 0; |
| } |
| |
| |
| bool SigintWatchdogHelper::Stop() { |
| bool had_pending_signal; |
| Mutex::ScopedLock lock(mutex_); |
| |
| { |
| Mutex::ScopedLock list_lock(list_mutex_); |
| |
| had_pending_signal = has_pending_signal_; |
| |
| if (--start_stop_count_ > 0) { |
| has_pending_signal_ = false; |
| return had_pending_signal; |
| } |
| |
| #ifdef __POSIX__ |
| // Set stopping now because it's only protected by list_mutex_. |
| stopping_ = true; |
| #endif |
| |
| watchdogs_.clear(); |
| } |
| |
| #ifndef __Fuchsia__ |
| #ifdef __POSIX__ |
| if (!has_running_thread_) { |
| has_pending_signal_ = false; |
| return had_pending_signal; |
| } |
| |
| // 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 |
| watchdog_disabled_ = true; |
| #endif |
| |
| had_pending_signal = has_pending_signal_; |
| has_pending_signal_ = false; |
| #endif |
| return had_pending_signal; |
| } |
| |
| |
| bool SigintWatchdogHelper::HasPendingSignal() { |
| Mutex::ScopedLock lock(list_mutex_); |
| |
| return has_pending_signal_; |
| } |
| |
| void SigintWatchdogHelper::Register(SigintWatchdogBase* wd) { |
| Mutex::ScopedLock lock(list_mutex_); |
| |
| watchdogs_.push_back(wd); |
| } |
| |
| void SigintWatchdogHelper::Unregister(SigintWatchdogBase* wd) { |
| Mutex::ScopedLock lock(list_mutex_); |
| |
| auto it = std::find(watchdogs_.begin(), watchdogs_.end(), wd); |
| |
| CHECK_NE(it, watchdogs_.end()); |
| watchdogs_.erase(it); |
| } |
| |
| |
| 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)); |
| #else |
| watchdog_disabled_ = false; |
| #endif |
| } |
| |
| |
| SigintWatchdogHelper::~SigintWatchdogHelper() { |
| start_stop_count_ = 0; |
| Stop(); |
| |
| #ifdef __POSIX__ |
| CHECK_EQ(has_running_thread_, false); |
| uv_sem_destroy(&sem_); |
| #endif |
| } |
| |
| 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) |