#include "node_watchdog.h" #include "node_internals.h" #include "util-inl.h" #include namespace node { Watchdog::Watchdog(v8::Isolate* isolate, uint64_t ms, bool* timed_out) : isolate_(isolate), timed_out_(timed_out) { int rc; loop_ = new uv_loop_t; CHECK(loop_); rc = uv_loop_init(loop_); if (rc != 0) { FatalError("node::Watchdog::Watchdog()", "Failed to initialize uv loop."); } rc = uv_async_init(loop_, &async_, &Watchdog::Async); 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); int rc = uv_loop_close(loop_); CHECK_EQ(0, rc); delete loop_; loop_ = nullptr; } 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::Async(uv_async_t* async) { Watchdog* w = ContainerOf(&Watchdog::async_, async); uv_stop(w->loop_); } 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) { 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); CHECK_EQ(0, pthread_sigmask(SIG_SETMASK, &sigmask, &sigmask)); 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