| // 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 "node_watchdog.h" |
| #include "node_internals.h" |
| #include "debug_utils.h" |
| #include <algorithm> |
| |
| 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<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_); |
| delete loop_; |
| loop_ = nullptr; |
| } |
| |
| |
| 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::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 |