You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

307 lines
7.4 KiB

// 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 <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);
int rc = uv_loop_close(loop_);
CHECK_EQ(0, rc);
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