// 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 #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(&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(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(&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 target) { Local constructor = env->NewFunctionTemplate(New); constructor->InstanceTemplate()->SetInternalFieldCount( TraceSigintWatchdog::kInternalFieldCount); Local 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& 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& 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& args) { TraceSigintWatchdog* watchdog; ASSIGN_OR_RETURN_UNWRAP(&watchdog, args.Holder()); SigintWatchdogHelper::GetInstance()->Unregister(watchdog); SigintWatchdogHelper::GetInstance()->Stop(); } TraceSigintWatchdog::TraceSigintWatchdog(Environment* env, Local object) : HandleWrap(env, object, reinterpret_cast(&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(&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(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; } #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 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(); } #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; 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 target, Local unused, Local 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)