diff options
Diffstat (limited to 'base/threading/watchdog.cc')
-rw-r--r-- | base/threading/watchdog.cc | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/base/threading/watchdog.cc b/base/threading/watchdog.cc new file mode 100644 index 0000000000..0e48c8e452 --- /dev/null +++ b/base/threading/watchdog.cc @@ -0,0 +1,183 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/threading/watchdog.h" + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/no_destructor.h" +#include "base/threading/platform_thread.h" + +namespace base { + +namespace { + +// When the debugger breaks (when we alarm), all the other alarms that are +// armed will expire (also alarm). To diminish this effect, we track any +// delay due to debugger breaks, and we *try* to adjust the effective start +// time of other alarms to step past the debugging break. +// Without this safety net, any alarm will typically trigger a host of follow +// on alarms from callers that specify old times. + +struct StaticData { + // Lock for access of static data... + Lock lock; + + // When did we last alarm and get stuck (for a while) in a debugger? + TimeTicks last_debugged_alarm_time; + + // How long did we sit on a break in the debugger? + TimeDelta last_debugged_alarm_delay; +}; + +StaticData* GetStaticData() { + static base::NoDestructor<StaticData> static_data; + return static_data.get(); +} + +} // namespace + +// Start thread running in a Disarmed state. +Watchdog::Watchdog(const TimeDelta& duration, + const std::string& thread_watched_name, + bool enabled) + : enabled_(enabled), + lock_(), + condition_variable_(&lock_), + state_(DISARMED), + duration_(duration), + thread_watched_name_(thread_watched_name), + delegate_(this) { + if (!enabled_) + return; // Don't start thread, or doing anything really. + enabled_ = PlatformThread::Create(0, // Default stack size. + &delegate_, + &handle_); + DCHECK(enabled_); +} + +// Notify watchdog thread, and wait for it to finish up. +Watchdog::~Watchdog() { + if (!enabled_) + return; + if (!IsJoinable()) + Cleanup(); + PlatformThread::Join(handle_); +} + +void Watchdog::Cleanup() { + if (!enabled_) + return; + AutoLock lock(lock_); + state_ = SHUTDOWN; + condition_variable_.Signal(); +} + +bool Watchdog::IsJoinable() { + if (!enabled_) + return true; + AutoLock lock(lock_); + return (state_ == JOINABLE); +} + +void Watchdog::Arm() { + ArmAtStartTime(TimeTicks::Now()); +} + +void Watchdog::ArmSomeTimeDeltaAgo(const TimeDelta& time_delta) { + ArmAtStartTime(TimeTicks::Now() - time_delta); +} + +// Start clock for watchdog. +void Watchdog::ArmAtStartTime(const TimeTicks start_time) { + AutoLock lock(lock_); + start_time_ = start_time; + state_ = ARMED; + // Force watchdog to wake up, and go to sleep with the timer ticking with the + // proper duration. + condition_variable_.Signal(); +} + +// Disable watchdog so that it won't do anything when time expires. +void Watchdog::Disarm() { + AutoLock lock(lock_); + state_ = DISARMED; + // We don't need to signal, as the watchdog will eventually wake up, and it + // will check its state and time, and act accordingly. +} + +void Watchdog::Alarm() { + DVLOG(1) << "Watchdog alarmed for " << thread_watched_name_; +} + +//------------------------------------------------------------------------------ +// Internal private methods that the watchdog thread uses. + +void Watchdog::ThreadDelegate::ThreadMain() { + SetThreadName(); + TimeDelta remaining_duration; + StaticData* static_data = GetStaticData(); + while (1) { + AutoLock lock(watchdog_->lock_); + while (DISARMED == watchdog_->state_) + watchdog_->condition_variable_.Wait(); + if (SHUTDOWN == watchdog_->state_) { + watchdog_->state_ = JOINABLE; + return; + } + DCHECK(ARMED == watchdog_->state_); + remaining_duration = watchdog_->duration_ - + (TimeTicks::Now() - watchdog_->start_time_); + if (remaining_duration.InMilliseconds() > 0) { + // Spurios wake? Timer drifts? Go back to sleep for remaining time. + watchdog_->condition_variable_.TimedWait(remaining_duration); + continue; + } + // We overslept, so this seems like a real alarm. + // Watch out for a user that stopped the debugger on a different alarm! + { + AutoLock static_lock(static_data->lock); + if (static_data->last_debugged_alarm_time > watchdog_->start_time_) { + // False alarm: we started our clock before the debugger break (last + // alarm time). + watchdog_->start_time_ += static_data->last_debugged_alarm_delay; + if (static_data->last_debugged_alarm_time > watchdog_->start_time_) + // Too many alarms must have taken place. + watchdog_->state_ = DISARMED; + continue; + } + } + watchdog_->state_ = DISARMED; // Only alarm at most once. + TimeTicks last_alarm_time = TimeTicks::Now(); + { + AutoUnlock unlock(watchdog_->lock_); + watchdog_->Alarm(); // Set a break point here to debug on alarms. + } + TimeDelta last_alarm_delay = TimeTicks::Now() - last_alarm_time; + if (last_alarm_delay <= TimeDelta::FromMilliseconds(2)) + continue; + // Ignore race of two alarms/breaks going off at roughly the same time. + AutoLock static_lock(static_data->lock); + // This was a real debugger break. + static_data->last_debugged_alarm_time = last_alarm_time; + static_data->last_debugged_alarm_delay = last_alarm_delay; + } +} + +void Watchdog::ThreadDelegate::SetThreadName() const { + std::string name = watchdog_->thread_watched_name_ + " Watchdog"; + PlatformThread::SetName(name); + DVLOG(1) << "Watchdog active: " << name; +} + +// static +void Watchdog::ResetStaticData() { + StaticData* static_data = GetStaticData(); + AutoLock lock(static_data->lock); + // See https://crbug.com/734232 for why this cannot be zero-initialized. + static_data->last_debugged_alarm_time = TimeTicks::Min(); + static_data->last_debugged_alarm_delay = TimeDelta(); +} + +} // namespace base |