// 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 #include #include #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& 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::CreateHangWatchStateForCurrentThread() { // Allocate a watch state object for this thread. std::unique_ptr hang_state = std::make_unique(); // 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::GetHangWatchStateForCurrentThread() { static NoDestructor> hang_watch_state; return hang_watch_state.get(); } } // namespace internal } // namespace base