// 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 "debug_utils.h" #include "env-inl.h" #include "node_errors.h" #include "node_internals.h" #include "node_watchdog.h" #include "util-inl.h" namespace node { 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(); } void SigintWatchdog::HandleSigint() { *received_signal_ = true; 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, 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_) it->HandleSigint(); 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(SigintWatchdog* wd) { Mutex::ScopedLock lock(list_mutex_); watchdogs_.push_back(wd); } void SigintWatchdogHelper::Unregister(SigintWatchdog* 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 node