| // Copyright 2020 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/hang_watcher.h" |
| |
| #include <algorithm> |
| #include <atomic> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/feature_list.h" |
| #include "base/no_destructor.h" |
| #include "base/synchronization/lock.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/threading/thread_checker.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/time/time.h" |
| |
| namespace base { |
| |
| // static |
| const base::Feature HangWatcher::kEnableHangWatcher{ |
| "EnableHangWatcher", base::FEATURE_DISABLED_BY_DEFAULT}; |
| |
| const base::TimeDelta HangWatchScope::kDefaultHangWatchTime = |
| base::TimeDelta::FromSeconds(10); |
| |
| namespace { |
| HangWatcher* g_instance = nullptr; |
| } |
| |
| constexpr const char* kThreadName = "HangWatcher"; |
| |
| // The time that the HangWatcher thread will sleep for between calls to |
| // Monitor(). Increasing or decreasing this does not modify the type of hangs |
| // that can be detected. It instead increases the probability that a call to |
| // Monitor() will happen at the right time to catch a hang. This has to be |
| // balanced with power/cpu use concerns as busy looping would catch amost all |
| // hangs but present unacceptable overhead. |
| const base::TimeDelta kMonitoringPeriod = base::TimeDelta::FromSeconds(10); |
| |
| HangWatchScope::HangWatchScope(TimeDelta timeout) { |
| internal::HangWatchState* current_hang_watch_state = |
| internal::HangWatchState::GetHangWatchStateForCurrentThread()->Get(); |
| |
| // TODO(crbug.com/1034046): Remove when all threads using HangWatchScope are |
| // monitored. Thread is not monitored, noop. |
| if (!current_hang_watch_state) { |
| return; |
| } |
| |
| DCHECK(current_hang_watch_state) |
| << "A scope can only be used on a thread that " |
| "registered for hang watching with HangWatcher::RegisterThread."; |
| |
| #if DCHECK_IS_ON() |
| previous_scope_ = current_hang_watch_state->GetCurrentHangWatchScope(); |
| current_hang_watch_state->SetCurrentHangWatchScope(this); |
| #endif |
| |
| // TODO(crbug.com/1034046): Check whether we are over deadline already for the |
| // previous scope here by issuing only one TimeTicks::Now() and resuing the |
| // value. |
| |
| previous_deadline_ = current_hang_watch_state->GetDeadline(); |
| TimeTicks deadline = TimeTicks::Now() + timeout; |
| current_hang_watch_state->SetDeadline(deadline); |
| } |
| |
| HangWatchScope::~HangWatchScope() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| internal::HangWatchState* current_hang_watch_state = |
| internal::HangWatchState::GetHangWatchStateForCurrentThread()->Get(); |
| |
| // TODO(crbug.com/1034046): Remove when all threads using HangWatchScope are |
| // monitored. Thread is not monitored, noop. |
| if (!current_hang_watch_state) { |
| return; |
| } |
| |
| // If a hang is currently being captured we should block here so execution |
| // stops and the relevant stack frames are recorded. |
| base::HangWatcher::GetInstance()->BlockIfCaptureInProgress(); |
| |
| #if DCHECK_IS_ON() |
| // Verify that no Scope was destructed out of order. |
| DCHECK_EQ(this, current_hang_watch_state->GetCurrentHangWatchScope()); |
| current_hang_watch_state->SetCurrentHangWatchScope(previous_scope_); |
| #endif |
| |
| // Reset the deadline to the value it had before entering this scope. |
| current_hang_watch_state->SetDeadline(previous_deadline_); |
| // TODO(crbug.com/1034046): Log when a HangWatchScope exits after its deadline |
| // and that went undetected by the HangWatcher. |
| } |
| |
| HangWatcher::HangWatcher(RepeatingClosure on_hang_closure) |
| : monitor_period_(kMonitoringPeriod), |
| should_monitor_(WaitableEvent::ResetPolicy::AUTOMATIC), |
| on_hang_closure_(std::move(on_hang_closure)), |
| thread_(this, kThreadName) { |
| // |thread_checker_| should not be bound to the constructing thread. |
| DETACH_FROM_THREAD(thread_checker_); |
| |
| should_monitor_.declare_only_used_while_idle(); |
| |
| DCHECK(!g_instance); |
| g_instance = this; |
| Start(); |
| } |
| |
| HangWatcher::~HangWatcher() { |
| DCHECK_EQ(g_instance, this); |
| DCHECK(watch_states_.empty()); |
| g_instance = nullptr; |
| Stop(); |
| } |
| |
| void HangWatcher::Start() { |
| thread_.Start(); |
| } |
| |
| void HangWatcher::Stop() { |
| keep_monitoring_.store(false, std::memory_order_relaxed); |
| should_monitor_.Signal(); |
| thread_.Join(); |
| } |
| |
| bool HangWatcher::IsWatchListEmpty() { |
| AutoLock auto_lock(watch_state_lock_); |
| return watch_states_.empty(); |
| } |
| |
| void HangWatcher::Run() { |
| // Monitor() should only run on |thread_|. Bind |thread_checker_| here to make |
| // sure of that. |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| while (keep_monitoring_.load(std::memory_order_relaxed)) { |
| // If there is nothing to watch sleep until there is. |
| if (IsWatchListEmpty()) { |
| should_monitor_.Wait(); |
| } else { |
| Monitor(); |
| } |
| |
| if (keep_monitoring_.load(std::memory_order_relaxed)) { |
| // Sleep until next scheduled monitoring. |
| should_monitor_.TimedWait(monitor_period_); |
| } |
| } |
| } |
| |
| // static |
| HangWatcher* HangWatcher::GetInstance() { |
| return g_instance; |
| } |
| |
| // static |
| void HangWatcher::RecordHang() { |
| base::debug::DumpWithoutCrashing(); |
| |
| // Defining |inhibit_tail_call_optimization| *after* calling |
| // DumpWithoutCrashing() prevents tail call optimization from omitting this |
| // function's address on the stack. |
| volatile int inhibit_tail_call_optimization = __LINE__; |
| ALLOW_UNUSED_LOCAL(inhibit_tail_call_optimization); |
| } |
| |
| ScopedClosureRunner HangWatcher::RegisterThread() { |
| AutoLock auto_lock(watch_state_lock_); |
| |
| watch_states_.push_back( |
| internal::HangWatchState::CreateHangWatchStateForCurrentThread()); |
| |
| // Now that there is a thread to monitor we wake the HangWatcher thread. |
| if (watch_states_.size() == 1) { |
| should_monitor_.Signal(); |
| } |
| |
| return ScopedClosureRunner(BindOnce(&HangWatcher::UnregisterThread, |
| Unretained(HangWatcher::GetInstance()))); |
| } |
| |
| void HangWatcher::Monitor() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| bool must_invoke_hang_closure = false; |
| { |
| AutoLock auto_lock(watch_state_lock_); |
| for (const auto& watch_state : watch_states_) { |
| if (watch_state->IsOverDeadline()) { |
| must_invoke_hang_closure = true; |
| break; |
| } |
| } |
| } |
| |
| if (must_invoke_hang_closure) { |
| capture_in_progress.store(true, std::memory_order_relaxed); |
| base::AutoLock scope_lock(capture_lock_); |
| |
| // Invoke the closure outside the scope of |watch_state_lock_| |
| // to prevent lock reentrancy. |
| on_hang_closure_.Run(); |
| |
| capture_in_progress.store(false, std::memory_order_relaxed); |
| } |
| |
| if (after_monitor_closure_for_testing_) { |
| after_monitor_closure_for_testing_.Run(); |
| } |
| } |
| |
| void HangWatcher::SetAfterMonitorClosureForTesting( |
| base::RepeatingClosure closure) { |
| after_monitor_closure_for_testing_ = std::move(closure); |
| } |
| |
| void HangWatcher::SetMonitoringPeriodForTesting(base::TimeDelta period) { |
| monitor_period_ = period; |
| } |
| |
| void HangWatcher::SignalMonitorEventForTesting() { |
| should_monitor_.Signal(); |
| } |
| |
| void HangWatcher::BlockIfCaptureInProgress() { |
| // Makes a best-effort attempt to block execution if a hang is currently being |
| // captured.Only block on |capture_lock| if |capture_in_progress| hints that |
| // it's already held to avoid serializing all threads on this function when no |
| // hang capture is in-progress. |
| if (capture_in_progress.load(std::memory_order_relaxed)) { |
| base::AutoLock hang_lock(capture_lock_); |
| } |
| } |
| |
| void HangWatcher::UnregisterThread() { |
| AutoLock auto_lock(watch_state_lock_); |
| |
| internal::HangWatchState* current_hang_watch_state = |
| internal::HangWatchState::GetHangWatchStateForCurrentThread()->Get(); |
| |
| auto it = |
| std::find_if(watch_states_.cbegin(), watch_states_.cend(), |
| [current_hang_watch_state]( |
| const std::unique_ptr<internal::HangWatchState>& state) { |
| return state.get() == current_hang_watch_state; |
| }); |
| |
| // Thread should be registered to get unregistered. |
| DCHECK(it != watch_states_.end()); |
| |
| watch_states_.erase(it); |
| } |
| |
| namespace internal { |
| |
| // |deadline_| starts at Max() to avoid validation problems |
| // when setting the first legitimate value. |
| HangWatchState::HangWatchState() { |
| // There should not exist a state object for this thread already. |
| DCHECK(!GetHangWatchStateForCurrentThread()->Get()); |
| |
| // Bind the new instance to this thread. |
| GetHangWatchStateForCurrentThread()->Set(this); |
| } |
| |
| HangWatchState::~HangWatchState() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| DCHECK_EQ(GetHangWatchStateForCurrentThread()->Get(), this); |
| GetHangWatchStateForCurrentThread()->Set(nullptr); |
| |
| #if DCHECK_IS_ON() |
| // Destroying the HangWatchState should not be done if there are live |
| // HangWatchScopes. |
| DCHECK(!current_hang_watch_scope_); |
| #endif |
| } |
| |
| // static |
| std::unique_ptr<HangWatchState> |
| HangWatchState::CreateHangWatchStateForCurrentThread() { |
| |
| // Allocate a watch state object for this thread. |
| std::unique_ptr<HangWatchState> hang_state = |
| std::make_unique<HangWatchState>(); |
| |
| // Setting the thread local worked. |
| DCHECK_EQ(GetHangWatchStateForCurrentThread()->Get(), hang_state.get()); |
| |
| // Transfer ownership to caller. |
| return hang_state; |
| } |
| |
| TimeTicks HangWatchState::GetDeadline() const { |
| return deadline_.load(std::memory_order_relaxed); |
| } |
| |
| void HangWatchState::SetDeadline(TimeTicks deadline) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| deadline_.store(deadline, std::memory_order_relaxed); |
| } |
| |
| bool HangWatchState::IsOverDeadline() const { |
| return TimeTicks::Now() > deadline_.load(std::memory_order_relaxed); |
| } |
| |
| #if DCHECK_IS_ON() |
| void HangWatchState::SetCurrentHangWatchScope(HangWatchScope* scope) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| current_hang_watch_scope_ = scope; |
| } |
| |
| HangWatchScope* HangWatchState::GetCurrentHangWatchScope() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| return current_hang_watch_scope_; |
| } |
| #endif |
| |
| // static |
| ThreadLocalPointer<HangWatchState>* |
| HangWatchState::GetHangWatchStateForCurrentThread() { |
| static NoDestructor<ThreadLocalPointer<HangWatchState>> hang_watch_state; |
| return hang_watch_state.get(); |
| } |
| |
| } // namespace internal |
| |
| } // namespace base |