#include "arkweb/build/features/features.h"
#include "base/threading/hang_watcher.h"
#include <algorithm>
#include <atomic>
#include <utility>
#include "base/containers/flat_map.h"
#include "base/debug/alias.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/debug/leak_annotations.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_macros.h"
#include "base/power_monitor/power_monitor.h"
#include "base/strings/string_number_conversions.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_checker.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/threading_features.h"
#include "base/time/default_tick_clock.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#if BUILDFLAG(ARKWEB_CRASHPAD)
#include "base/logging.h"
#include "base/command_line.h"
#include <fstream>
#include "arkweb/chromium_ext/gpu/ipc/service/gpu_hang_adapter.h"
#endif
#if BUILDFLAG(ARKWEB_CRASHPAD) && !defined(COMPONENT_BUILD)
extern void ReportRenderFreeze(int32_t pid, const std::string& processName, const std::string& freezeMsg, int32_t uid);
#endif
namespace base {
namespace {
enum class LoggingLevel { kNone = 0, kUmaOnly = 1, kUmaAndCrash = 2 };
HangWatcher* g_instance = nullptr;
constinit thread_local internal::HangWatchState* hang_watch_state = nullptr;
std::atomic<bool> g_use_hang_watcher{false};
std::atomic<HangWatcher::ProcessType> g_hang_watcher_process_type{
HangWatcher::ProcessType::kBrowserProcess};
std::atomic<LoggingLevel> g_threadpool_log_level{LoggingLevel::kNone};
std::atomic<LoggingLevel> g_io_thread_log_level{LoggingLevel::kNone};
std::atomic<LoggingLevel> g_main_thread_log_level{LoggingLevel::kNone};
std::atomic<LoggingLevel> g_compositor_thread_log_level{LoggingLevel::kNone};
#if BUILDFLAG(IS_ARKWEB)
std::atomic<LoggingLevel> g_compositor_gpu_thread_log_level{LoggingLevel::kNone};
std::atomic<LoggingLevel> g_in_process_gpu_thread_log_level{LoggingLevel::kNone};
#endif
std::atomic<bool> g_keep_monitoring{true};
std::atomic<bool> g_shutting_down{false};
#if BUILDFLAG(ARKWEB_CRASHPAD)
int32_t GetPid() {
std::ifstream input_file("/proc/self/status");
if (!input_file.is_open()) {
LOG(ERROR) << "Error: Could not open proc/self/status";
return 0;
}
std::string line;
while (std::getline(input_file, line)) {
if (line.find("NSpid:") != 0) {
continue;
}
size_t pos = line.find(":");
if (pos != std::string::npos) {
std::string valueStr = line.substr(pos + 1);
return std::stoi(valueStr);
}
}
LOG(ERROR) << "Error: Failed to read pid from /proc/self/status";
return 0;
}
std::string GetProcessName() {
std::ifstream input_file("/proc/self/cmdline");
if (!input_file.is_open()) {
LOG(ERROR) << "Error: Could not open /proc/self/cmdline";
return "";
}
std::string processName = "";
if (!std::getline(input_file, processName)) {
LOG(ERROR) << "Error: Failed to read process name from /proc/self/cmdline";
}
return processName;
}
#endif
void LogStatusHistogram(HangWatcher::ThreadType thread_type,
int count,
TimeTicks sample_ticks,
TimeDelta monitoring_period) {
const bool any_thread_hung = count >= 1;
const bool shutting_down = g_shutting_down.load(std::memory_order_relaxed);
const HangWatcher::ProcessType process_type =
g_hang_watcher_process_type.load(std::memory_order_relaxed);
switch (process_type) {
case HangWatcher::ProcessType::kUnknownProcess:
break;
case HangWatcher::ProcessType::kBrowserProcess:
switch (thread_type) {
case HangWatcher::ThreadType::kIOThread:
if (shutting_down) {
UMA_HISTOGRAM_BOOLEAN(
"HangWatcher.IsThreadHung.BrowserProcess.IOThread.Shutdown",
any_thread_hung);
} else {
UMA_HISTOGRAM_BOOLEAN(
"HangWatcher.IsThreadHung.BrowserProcess.IOThread.Normal",
any_thread_hung);
}
break;
case HangWatcher::ThreadType::kMainThread:
if (shutting_down) {
UMA_HISTOGRAM_BOOLEAN(
"HangWatcher.IsThreadHung.BrowserProcess.UIThread.Shutdown",
any_thread_hung);
} else {
UMA_HISTOGRAM_BOOLEAN(
"HangWatcher.IsThreadHung.BrowserProcess.UIThread.Normal",
any_thread_hung);
}
break;
case HangWatcher::ThreadType::kCompositorThread:
CHECK(false) << "kCompositorThread type should not be logged for "
"BrowserProcess";
case HangWatcher::ThreadType::kThreadPoolThread:
break;
#if BUILDFLAG(IS_ARKWEB)
case HangWatcher::ThreadType::kCompositorGpuThread:
case HangWatcher::ThreadType::kInProcessGpuThread:
if (any_thread_hung) {
#if BUILDFLAG(ARKWEB_CRASHPAD) && !defined(COMPONENT_BUILD) && !BUILDFLAG(ARKWEB_ASAN)
gpu::LogGpuHungEvent(static_cast<int32_t>(thread_type), count);
#endif
}
break;
#endif
}
break;
case HangWatcher::ProcessType::kGPUProcess:
CHECK(!shutting_down);
switch (thread_type) {
case HangWatcher::ThreadType::kIOThread:
UMA_HISTOGRAM_SPLIT_BY_PROCESS_PRIORITY(
UMA_HISTOGRAM_BOOLEAN, sample_ticks, monitoring_period,
"HangWatcher.IsThreadHung.GpuProcess.IOThread", any_thread_hung);
break;
case HangWatcher::ThreadType::kMainThread:
UMA_HISTOGRAM_SPLIT_BY_PROCESS_PRIORITY(
UMA_HISTOGRAM_BOOLEAN, sample_ticks, monitoring_period,
"HangWatcher.IsThreadHung.GpuProcess.MainThread",
any_thread_hung);
break;
case HangWatcher::ThreadType::kCompositorThread:
UMA_HISTOGRAM_SPLIT_BY_PROCESS_PRIORITY(
UMA_HISTOGRAM_BOOLEAN, sample_ticks, monitoring_period,
"HangWatcher.IsThreadHung.GpuProcess.CompositorThread",
any_thread_hung);
break;
case HangWatcher::ThreadType::kThreadPoolThread:
break;
#if BUILDFLAG(IS_ARKWEB)
case HangWatcher::ThreadType::kCompositorGpuThread:
case HangWatcher::ThreadType::kInProcessGpuThread:
break;
#endif
}
break;
case HangWatcher::ProcessType::kRendererProcess:
CHECK(!shutting_down);
switch (thread_type) {
case HangWatcher::ThreadType::kIOThread:
UMA_HISTOGRAM_SPLIT_BY_PROCESS_PRIORITY(
UMA_HISTOGRAM_BOOLEAN, sample_ticks, monitoring_period,
"HangWatcher.IsThreadHung.RendererProcess.IOThread",
any_thread_hung);
break;
case HangWatcher::ThreadType::kMainThread:
#if BUILDFLAG(ARKWEB_CRASHPAD) && !defined(COMPONENT_BUILD) && !BUILDFLAG(ARKWEB_ASAN)
if (any_thread_hung) {
ReportRenderFreeze(GetPid(), GetProcessName(), "render freeze", static_cast<int32_t>(getuid()));
}
#endif
UMA_HISTOGRAM_SPLIT_BY_PROCESS_PRIORITY(
UMA_HISTOGRAM_BOOLEAN, sample_ticks, monitoring_period,
"HangWatcher.IsThreadHung.RendererProcess.MainThread",
any_thread_hung);
break;
case HangWatcher::ThreadType::kCompositorThread:
UMA_HISTOGRAM_SPLIT_BY_PROCESS_PRIORITY(
UMA_HISTOGRAM_BOOLEAN, sample_ticks, monitoring_period,
"HangWatcher.IsThreadHung.RendererProcess.CompositorThread",
any_thread_hung);
break;
case HangWatcher::ThreadType::kThreadPoolThread:
break;
#if BUILDFLAG(IS_ARKWEB)
case HangWatcher::ThreadType::kCompositorGpuThread:
case HangWatcher::ThreadType::kInProcessGpuThread:
break;
#endif
}
break;
case HangWatcher::ProcessType::kUtilityProcess:
CHECK(!shutting_down);
switch (thread_type) {
case HangWatcher::ThreadType::kIOThread:
UMA_HISTOGRAM_BOOLEAN(
"HangWatcher.IsThreadHung.UtilityProcess.IOThread",
any_thread_hung);
break;
case HangWatcher::ThreadType::kMainThread:
UMA_HISTOGRAM_BOOLEAN(
"HangWatcher.IsThreadHung.UtilityProcess.MainThread",
any_thread_hung);
break;
case HangWatcher::ThreadType::kCompositorThread:
CHECK(false) << "kCompositorThread type should not be logged for "
"UtilityProcess";
case HangWatcher::ThreadType::kThreadPoolThread:
break;
#if BUILDFLAG(IS_ARKWEB)
case HangWatcher::ThreadType::kCompositorGpuThread:
case HangWatcher::ThreadType::kInProcessGpuThread:
break;
#endif
}
break;
}
}
bool ThreadTypeLoggingLevelGreaterOrEqual(HangWatcher::ThreadType thread_type,
LoggingLevel logging_level) {
switch (thread_type) {
case HangWatcher::ThreadType::kIOThread:
return g_io_thread_log_level.load(std::memory_order_relaxed) >=
logging_level;
case HangWatcher::ThreadType::kMainThread:
return g_main_thread_log_level.load(std::memory_order_relaxed) >=
logging_level;
case HangWatcher::ThreadType::kThreadPoolThread:
return g_threadpool_log_level.load(std::memory_order_relaxed) >=
logging_level;
#if BUILDFLAG(IS_ARKWEB)
case HangWatcher::ThreadType::kCompositorGpuThread:
return g_compositor_gpu_thread_log_level.load(std::memory_order_relaxed) >=
logging_level;
case HangWatcher::ThreadType::kInProcessGpuThread:
return g_in_process_gpu_thread_log_level.load(std::memory_order_relaxed) >=
logging_level;
#endif
case HangWatcher::ThreadType::kCompositorThread:
return g_compositor_thread_log_level.load(std::memory_order_relaxed) >=
logging_level;
}
}
}
BASE_FEATURE(kEnableHangWatcher,
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS) || \
BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ARKWEB)
FEATURE_ENABLED_BY_DEFAULT
#else
FEATURE_DISABLED_BY_DEFAULT
#endif
);
BASE_FEATURE(kEnableHangWatcherOnGpuProcess, FEATURE_DISABLED_BY_DEFAULT);
const char kBrowserProcessIoThreadLogLevelParam[] = "io_thread_log_level";
const char kBrowserProcessUiThreadLogLevelParam[] = "ui_thread_log_level";
const char kBrowserProcessThreadPoolLogLevelParam[] = "threadpool_log_level";
constexpr base::FeatureParam<int> kIOThreadLogLevel{
&kEnableHangWatcher, kBrowserProcessIoThreadLogLevelParam,
static_cast<int>(LoggingLevel::kUmaOnly)};
constexpr base::FeatureParam<int> kUIThreadLogLevel{
&kEnableHangWatcher, kBrowserProcessUiThreadLogLevelParam,
static_cast<int>(LoggingLevel::kUmaOnly)};
constexpr base::FeatureParam<int> kThreadPoolLogLevel{
&kEnableHangWatcher, kBrowserProcessThreadPoolLogLevelParam,
static_cast<int>(LoggingLevel::kUmaOnly)};
#if BUILDFLAG(IS_ARKWEB)
constexpr base::FeatureParam<int> kCompositorGpuThreadLogLevel{
&kEnableHangWatcher, "compositor_gpu_thread_log_level",
static_cast<int>(LoggingLevel::kUmaOnly)};
constexpr base::FeatureParam<int> kInProcessGpuThreadLogLevel{
&kEnableHangWatcher, "in_process_gpu_thread_log_level",
static_cast<int>(LoggingLevel::kUmaOnly)};
#endif
const char kGpuProcessIoThreadLogLevelParam[] =
"gpu_process_io_thread_log_level";
const char kGpuProcessMainThreadLogLevelParam[] =
"gpu_process_main_thread_log_level";
const char kGpuProcessCompositorThreadLogLevelParam[] =
"gpu_process_compositor_thread_log_level";
const char kGpuProcessThreadPoolLogLevelParam[] =
"gpu_process_threadpool_log_level";
constexpr base::FeatureParam<int> kGPUProcessIOThreadLogLevel{
&kEnableHangWatcherOnGpuProcess, kGpuProcessIoThreadLogLevelParam,
static_cast<int>(LoggingLevel::kUmaOnly)};
constexpr base::FeatureParam<int> kGPUProcessMainThreadLogLevel{
&kEnableHangWatcherOnGpuProcess, kGpuProcessMainThreadLogLevelParam,
static_cast<int>(LoggingLevel::kUmaOnly)};
constexpr base::FeatureParam<int> kGPUProcessCompositorThreadLogLevel{
&kEnableHangWatcherOnGpuProcess, kGpuProcessCompositorThreadLogLevelParam,
static_cast<int>(LoggingLevel::kUmaOnly)};
constexpr base::FeatureParam<int> kGPUProcessThreadPoolLogLevel{
&kEnableHangWatcherOnGpuProcess, kGpuProcessThreadPoolLogLevelParam,
static_cast<int>(LoggingLevel::kUmaOnly)};
const char kRendererProcessIoThreadLogLevelParam[] =
"renderer_process_io_thread_log_level";
const char kRendererProcessMainThreadLogLevelParam[] =
"renderer_process_main_thread_log_level";
const char kRendererProcessThreadPoolLogLevelParam[] =
"renderer_process_threadpool_log_level";
const char kRendererProcessCompositorThreadLogLevelParam[] =
"renderer_process_compositor_thread_log_level";
constexpr base::FeatureParam<int> kRendererProcessIOThreadLogLevel{
&kEnableHangWatcher, kRendererProcessIoThreadLogLevelParam,
static_cast<int>(LoggingLevel::kUmaOnly)};
constexpr base::FeatureParam<int> kRendererProcessMainThreadLogLevel{
&kEnableHangWatcher, kRendererProcessMainThreadLogLevelParam,
static_cast<int>(LoggingLevel::kUmaOnly)};
constexpr base::FeatureParam<int> kRendererProcessThreadPoolLogLevel{
&kEnableHangWatcher, kRendererProcessThreadPoolLogLevelParam,
static_cast<int>(LoggingLevel::kUmaOnly)};
constexpr base::FeatureParam<int> kRendererProcessCompositorThreadLogLevel{
&kEnableHangWatcher, kRendererProcessCompositorThreadLogLevelParam,
static_cast<int>(LoggingLevel::kUmaOnly)};
const char kUtilityProcessIoThreadLogLevelParam[] =
"utility_process_io_thread_log_level";
const char kUtilityProcessMainThreadLogLevelParam[] =
"utility_process_main_thread_log_level";
const char kUtilityProcessThreadPoolLogLevelParam[] =
"utility_process_threadpool_log_level";
constexpr base::FeatureParam<int> kUtilityProcessIOThreadLogLevel{
&kEnableHangWatcher, kUtilityProcessIoThreadLogLevelParam,
static_cast<int>(LoggingLevel::kUmaOnly)};
constexpr base::FeatureParam<int> kUtilityProcessMainThreadLogLevel{
&kEnableHangWatcher, kUtilityProcessMainThreadLogLevelParam,
static_cast<int>(LoggingLevel::kUmaOnly)};
constexpr base::FeatureParam<int> kUtilityProcessThreadPoolLogLevel{
&kEnableHangWatcher, kUtilityProcessThreadPoolLogLevelParam,
static_cast<int>(LoggingLevel::kUmaOnly)};
constexpr const char* kThreadName = "HangWatcher";
const char kHangWatcherMonitoringPeriodParam[] =
"hang_watcher_monitoring_period";
constexpr base::FeatureParam<base::TimeDelta> kHangWatcherMonitoringPeriod(
&kEnableHangWatcher,
kHangWatcherMonitoringPeriodParam,
base::Seconds(10));
WatchHangsInScope::WatchHangsInScope(TimeDelta timeout) {
internal::HangWatchState* current_hang_watch_state =
HangWatcher::IsEnabled()
? internal::HangWatchState::GetHangWatchStateForCurrentThread()
: nullptr;
DCHECK(timeout >= base::TimeDelta()) << "Negative timeouts are invalid.";
if (!current_hang_watch_state) {
took_effect_ = false;
return;
}
#if DCHECK_IS_ON()
previous_watch_hangs_in_scope_ =
current_hang_watch_state->GetCurrentWatchHangsInScope();
current_hang_watch_state->SetCurrentWatchHangsInScope(this);
#endif
auto [old_flags, old_deadline] =
current_hang_watch_state->GetFlagsAndDeadline();
previous_deadline_ = old_deadline;
TimeTicks deadline = TimeTicks::Now() + timeout;
current_hang_watch_state->SetDeadline(deadline);
current_hang_watch_state->IncrementNestingLevel();
const bool hangs_ignored_for_current_scope =
internal::HangWatchDeadline::IsFlagSet(
internal::HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope,
old_flags);
if (hangs_ignored_for_current_scope) {
current_hang_watch_state->UnsetIgnoreCurrentWatchHangsInScope();
set_hangs_ignored_on_exit_ = true;
}
}
WatchHangsInScope::~WatchHangsInScope() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!took_effect_) {
return;
}
auto* const state =
internal::HangWatchState::GetHangWatchStateForCurrentThread();
if (!state) {
return;
}
if (state->IsFlagSet(internal::HangWatchDeadline::Flag::kShouldBlockOnHang)) {
base::HangWatcher::GetInstance()->BlockIfCaptureInProgress();
}
#if DCHECK_IS_ON()
DCHECK_EQ(this, state->GetCurrentWatchHangsInScope());
state->SetCurrentWatchHangsInScope(previous_watch_hangs_in_scope_);
#endif
if (state->nesting_level() == 1) {
state->UnsetIgnoreCurrentWatchHangsInScope();
} else if (set_hangs_ignored_on_exit_) {
state->SetIgnoreCurrentWatchHangsInScope();
}
state->SetDeadline(previous_deadline_);
state->DecrementNestingLevel();
}
void HangWatcher::InitializeOnMainThread(ProcessType process_type,
bool emit_crashes) {
DCHECK(!g_use_hang_watcher);
DCHECK(g_io_thread_log_level == LoggingLevel::kNone);
DCHECK(g_main_thread_log_level == LoggingLevel::kNone);
DCHECK(g_threadpool_log_level == LoggingLevel::kNone);
bool enable_hang_watcher =
(process_type == ProcessType::kGPUProcess
? base::FeatureList::IsEnabled(kEnableHangWatcherOnGpuProcess)
: base::FeatureList::IsEnabled(kEnableHangWatcher));
g_use_hang_watcher.store(enable_hang_watcher, std::memory_order_relaxed);
g_hang_watcher_process_type.store(process_type, std::memory_order_relaxed);
if (!enable_hang_watcher) {
return;
}
if (process_type == HangWatcher::ProcessType::kBrowserProcess) {
if (emit_crashes) {
g_io_thread_log_level.store(
static_cast<LoggingLevel>(LoggingLevel::kUmaAndCrash),
std::memory_order_relaxed);
g_main_thread_log_level.store(
static_cast<LoggingLevel>(LoggingLevel::kUmaAndCrash),
std::memory_order_relaxed);
} else {
g_io_thread_log_level.store(
static_cast<LoggingLevel>(kIOThreadLogLevel.Get()),
std::memory_order_relaxed);
g_main_thread_log_level.store(
static_cast<LoggingLevel>(kUIThreadLogLevel.Get()),
std::memory_order_relaxed);
}
#if BUILDFLAG(IS_ARKWEB)
g_compositor_gpu_thread_log_level.store(
static_cast<LoggingLevel>(kCompositorGpuThreadLogLevel.Get()),
std::memory_order_relaxed);
g_in_process_gpu_thread_log_level.store(
static_cast<LoggingLevel>(kInProcessGpuThreadLogLevel.Get()),
std::memory_order_relaxed);
#endif
g_threadpool_log_level.store(
static_cast<LoggingLevel>(kThreadPoolLogLevel.Get()),
std::memory_order_relaxed);
} else if (process_type == HangWatcher::ProcessType::kGPUProcess) {
g_threadpool_log_level.store(
static_cast<LoggingLevel>(kGPUProcessThreadPoolLogLevel.Get()),
std::memory_order_relaxed);
g_io_thread_log_level.store(
static_cast<LoggingLevel>(kGPUProcessIOThreadLogLevel.Get()),
std::memory_order_relaxed);
g_main_thread_log_level.store(
static_cast<LoggingLevel>(kGPUProcessMainThreadLogLevel.Get()),
std::memory_order_relaxed);
g_compositor_thread_log_level.store(
static_cast<LoggingLevel>(kGPUProcessCompositorThreadLogLevel.Get()),
std::memory_order_relaxed);
} else if (process_type == HangWatcher::ProcessType::kRendererProcess) {
g_threadpool_log_level.store(
static_cast<LoggingLevel>(kRendererProcessThreadPoolLogLevel.Get()),
std::memory_order_relaxed);
g_io_thread_log_level.store(
static_cast<LoggingLevel>(kRendererProcessIOThreadLogLevel.Get()),
std::memory_order_relaxed);
g_main_thread_log_level.store(
static_cast<LoggingLevel>(kRendererProcessMainThreadLogLevel.Get()),
std::memory_order_relaxed);
g_compositor_thread_log_level.store(
static_cast<LoggingLevel>(
kRendererProcessCompositorThreadLogLevel.Get()),
std::memory_order_relaxed);
} else if (process_type == HangWatcher::ProcessType::kUtilityProcess) {
g_threadpool_log_level.store(
static_cast<LoggingLevel>(kUtilityProcessThreadPoolLogLevel.Get()),
std::memory_order_relaxed);
g_io_thread_log_level.store(
static_cast<LoggingLevel>(kUtilityProcessIOThreadLogLevel.Get()),
std::memory_order_relaxed);
g_main_thread_log_level.store(
static_cast<LoggingLevel>(kUtilityProcessMainThreadLogLevel.Get()),
std::memory_order_relaxed);
}
}
void HangWatcher::UninitializeOnMainThreadForTesting() {
g_use_hang_watcher.store(false, std::memory_order_relaxed);
g_threadpool_log_level.store(LoggingLevel::kNone, std::memory_order_relaxed);
g_io_thread_log_level.store(LoggingLevel::kNone, std::memory_order_relaxed);
g_main_thread_log_level.store(LoggingLevel::kNone, std::memory_order_relaxed);
#if BUILDFLAG(IS_ARKWEB)
g_in_process_gpu_thread_log_level.store(LoggingLevel::kNone, std::memory_order_relaxed);
g_compositor_gpu_thread_log_level.store(LoggingLevel::kNone, std::memory_order_relaxed);
#endif
g_compositor_thread_log_level.store(LoggingLevel::kNone,
std::memory_order_relaxed);
g_shutting_down.store(false, std::memory_order_relaxed);
}
bool HangWatcher::IsEnabled() {
return g_use_hang_watcher.load(std::memory_order_relaxed);
}
bool HangWatcher::IsThreadPoolHangWatchingEnabled() {
return g_threadpool_log_level.load(std::memory_order_relaxed) !=
LoggingLevel::kNone;
}
bool HangWatcher::IsIOThreadHangWatchingEnabled() {
return g_io_thread_log_level.load(std::memory_order_relaxed) !=
LoggingLevel::kNone;
}
#if BUILDFLAG(IS_ARKWEB)
bool HangWatcher::IsCompositorGpuThreadHangWatchingEnabled() {
return g_compositor_gpu_thread_log_level.load(std::memory_order_relaxed) !=
LoggingLevel::kNone;
}
bool HangWatcher::IsInProcessGpuThreadHangWatchingEnabled() {
return g_in_process_gpu_thread_log_level.load(std::memory_order_relaxed) !=
LoggingLevel::kNone;
}
#endif
bool HangWatcher::IsCompositorThreadHangWatchingEnabled() {
return g_compositor_thread_log_level.load(std::memory_order_relaxed) !=
LoggingLevel::kNone;
}
bool HangWatcher::IsCrashReportingEnabled() {
if (g_main_thread_log_level.load(std::memory_order_relaxed) ==
LoggingLevel::kUmaAndCrash) {
return true;
}
if (g_io_thread_log_level.load(std::memory_order_relaxed) ==
LoggingLevel::kUmaAndCrash) {
return true;
}
if (g_threadpool_log_level.load(std::memory_order_relaxed) ==
LoggingLevel::kUmaAndCrash) {
return true;
}
return false;
}
void HangWatcher::InvalidateActiveExpectations() {
auto* const state =
internal::HangWatchState::GetHangWatchStateForCurrentThread();
if (!state) {
return;
}
state->SetIgnoreCurrentWatchHangsInScope();
}
void HangWatcher::SetShuttingDown() {
bool was_shutting_down =
g_shutting_down.exchange(true, std::memory_order_relaxed);
DCHECK(!was_shutting_down);
}
HangWatcher::HangWatcher()
: monitoring_period_(kHangWatcherMonitoringPeriod.Get()),
should_monitor_(WaitableEvent::ResetPolicy::AUTOMATIC),
thread_(this, kThreadName),
tick_clock_(base::DefaultTickClock::GetInstance()),
memory_pressure_listener_registration_(
FROM_HERE,
base::MemoryPressureListenerTag::kHangWatcher,
this) {
DETACH_FROM_THREAD(hang_watcher_thread_checker_);
should_monitor_.declare_only_used_while_idle();
DCHECK(!g_instance);
g_instance = this;
}
void HangWatcher::CreateHangWatcherInstance() {
DCHECK(!g_instance);
g_instance = new base::HangWatcher();
ANNOTATE_LEAKING_OBJECT_PTR(g_instance);
}
debug::ScopedCrashKeyString
HangWatcher::GetTimeSinceLastCriticalMemoryPressureCrashKey() {
DCHECK_CALLED_ON_VALID_THREAD(hang_watcher_thread_checker_);
constexpr debug::CrashKeySize kCrashKeyContentSize =
debug::CrashKeySize::Size32;
DCHECK_GE(static_cast<uint64_t>(kCrashKeyContentSize),
base::NumberToString(std::numeric_limits<int64_t>::max()).size());
static debug::CrashKeyString* crash_key = AllocateCrashKeyString(
"seconds-since-last-memory-pressure", kCrashKeyContentSize);
const base::TimeTicks last_critical_memory_pressure_time =
last_critical_memory_pressure_.load(std::memory_order_relaxed);
if (last_critical_memory_pressure_time.is_null()) {
constexpr char kNoMemoryPressureMsg[] = "No critical memory pressure";
static_assert(
std::size(kNoMemoryPressureMsg) <=
static_cast<uint64_t>(kCrashKeyContentSize),
"The crash key is too small to hold \"No critical memory pressure\".");
return debug::ScopedCrashKeyString(crash_key, kNoMemoryPressureMsg);
} else {
base::TimeDelta time_since_last_critical_memory_pressure =
base::TimeTicks::Now() - last_critical_memory_pressure_time;
return debug::ScopedCrashKeyString(
crash_key, base::NumberToString(
time_since_last_critical_memory_pressure.InSeconds()));
}
}
std::string HangWatcher::GetTimeSinceLastSystemPowerResumeCrashKeyValue()
const {
DCHECK_CALLED_ON_VALID_THREAD(hang_watcher_thread_checker_);
const TimeTicks last_system_power_resume_time =
PowerMonitor::GetInstance()->GetLastSystemResumeTime();
if (last_system_power_resume_time.is_null()) {
return "Never suspended";
}
if (last_system_power_resume_time == TimeTicks::Max()) {
return "Power suspended";
}
const TimeDelta time_since_last_system_resume =
TimeTicks::Now() - last_system_power_resume_time;
return NumberToString(time_since_last_system_resume.InSeconds());
}
void HangWatcher::OnMemoryPressure(MemoryPressureLevel memory_pressure_level) {
if (memory_pressure_level == MEMORY_PRESSURE_LEVEL_CRITICAL) {
last_critical_memory_pressure_.store(base::TimeTicks::Now(),
std::memory_order_relaxed);
}
}
HangWatcher::~HangWatcher() {
DCHECK_CALLED_ON_VALID_THREAD(constructing_thread_checker_);
DCHECK_EQ(g_instance, this);
DCHECK(watch_states_.empty());
g_instance = nullptr;
if (thread_started_) {
Stop();
}
}
void HangWatcher::Start() {
thread_.Start();
thread_started_ = true;
}
void HangWatcher::Stop() {
g_keep_monitoring.store(false, std::memory_order_relaxed);
should_monitor_.Signal();
thread_.Join();
thread_started_ = false;
g_keep_monitoring.store(true, std::memory_order_relaxed);
}
bool HangWatcher::IsWatchingThreads() {
AutoLock auto_lock(watch_state_lock_);
return !watch_states_.empty();
}
void HangWatcher::Wait() {
while (true) {
constexpr base::TimeDelta kWaitDriftTolerance = base::Milliseconds(100);
const base::TimeTicks time_before_wait = tick_clock_->NowTicks();
const bool was_signaled = should_monitor_.TimedWait(monitoring_period_);
if (after_wait_callback_) {
after_wait_callback_.Run(time_before_wait);
}
const base::TimeTicks time_after_wait = tick_clock_->NowTicks();
const base::TimeDelta wait_time = time_after_wait - time_before_wait;
const bool wait_was_normal =
wait_time <= (monitoring_period_ + kWaitDriftTolerance);
if (!wait_was_normal) {
base::AutoLock auto_lock(watch_state_lock_);
base::TimeTicks latest_deadline;
for (const auto& state : watch_states_) {
base::TimeTicks deadline = state->GetDeadline();
if (deadline > latest_deadline) {
latest_deadline = deadline;
}
}
deadline_ignore_threshold_ = latest_deadline;
}
if (wait_was_normal || was_signaled) {
return;
}
}
}
void HangWatcher::Run() {
DCHECK_CALLED_ON_VALID_THREAD(hang_watcher_thread_checker_);
while (g_keep_monitoring.load(std::memory_order_relaxed)) {
Wait();
if (IsWatchingThreads() &&
g_keep_monitoring.load(std::memory_order_relaxed)) {
Monitor();
if (after_monitor_closure_for_testing_) {
after_monitor_closure_for_testing_.Run();
}
}
}
}
HangWatcher* HangWatcher::GetInstance() {
return g_instance;
}
void HangWatcher::RecordHang() {
base::debug::DumpWithoutCrashing();
NO_CODE_FOLDING();
}
ScopedClosureRunner HangWatcher::RegisterThreadInternal(
ThreadType thread_type) {
AutoLock auto_lock(watch_state_lock_);
CHECK(base::FeatureList::GetInstance());
if (!ThreadTypeLoggingLevelGreaterOrEqual(thread_type,
LoggingLevel::kUmaOnly)) {
return ScopedClosureRunner(base::DoNothing());
}
watch_states_.push_back(
internal::HangWatchState::CreateHangWatchStateForCurrentThread(
thread_type));
return ScopedClosureRunner(BindOnce(&HangWatcher::UnregisterThread,
Unretained(HangWatcher::GetInstance())));
}
ScopedClosureRunner HangWatcher::RegisterThread(ThreadType thread_type) {
if (!GetInstance()) {
return ScopedClosureRunner();
}
return GetInstance()->RegisterThreadInternal(thread_type);
}
base::TimeTicks HangWatcher::WatchStateSnapShot::GetHighestDeadline() const {
DCHECK(IsActionable());
return hung_watch_state_copies_.back().deadline;
}
HangWatcher::WatchStateSnapShot::WatchStateSnapShot() = default;
void HangWatcher::WatchStateSnapShot::Init(
const HangWatchStates& watch_states,
base::TimeTicks deadline_ignore_threshold,
base::TimeDelta monitoring_period) {
DCHECK(!initialized_);
initialized_ = true;
const base::TimeTicks now = base::TimeTicks::Now();
bool all_threads_marked = true;
bool found_deadline_before_ignore_threshold = false;
constexpr size_t kHangCountArraySize =
static_cast<std::size_t>(base::HangWatcher::ThreadType::kMax) + 1;
std::array<int, kHangCountArraySize> hung_counts_per_thread_type;
constexpr int kInvalidHangCount = -1;
hung_counts_per_thread_type.fill(kInvalidHangCount);
bool any_hung_thread_has_dumping_enabled = false;
for (const auto& watch_state : watch_states) {
uint64_t flags;
TimeTicks deadline;
std::tie(flags, deadline) = watch_state->GetFlagsAndDeadline();
if (deadline <= deadline_ignore_threshold) {
found_deadline_before_ignore_threshold = true;
}
if (internal::HangWatchDeadline::IsFlagSet(
internal::HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope,
flags)) {
continue;
}
const size_t hang_count_index =
static_cast<size_t>(watch_state.get()->thread_type());
if (hung_counts_per_thread_type[hang_count_index] == kInvalidHangCount) {
hung_counts_per_thread_type[hang_count_index] = 0;
}
if (deadline <= now) {
++hung_counts_per_thread_type[hang_count_index];
if (ThreadTypeLoggingLevelGreaterOrEqual(watch_state.get()->thread_type(),
LoggingLevel::kUmaAndCrash)) {
any_hung_thread_has_dumping_enabled = true;
}
if (ThreadTypeLoggingLevelGreaterOrEqual(watch_state.get()->thread_type(),
LoggingLevel::kUmaOnly)) {
const PlatformThreadId thread_id = watch_state.get()->GetThreadID();
const auto track = perfetto::Track::FromPointer(
this, perfetto::ThreadTrack::ForThread(thread_id.raw()));
TRACE_EVENT_BEGIN("latency", "HangWatcher::ThreadHung", track,
now - monitoring_period);
TRACE_EVENT_END("latency", track, now);
}
bool thread_marked = watch_state->SetShouldBlockOnHang(flags, deadline);
if (thread_marked && all_threads_marked) {
hung_watch_state_copies_.push_back(
WatchStateCopy{deadline, watch_state.get()->GetThreadID()});
} else {
all_threads_marked = false;
}
}
}
bool any_critical_thread_hung = false;
bool any_thread_hung = false;
for (size_t i = 0; i < kHangCountArraySize; ++i) {
const int hang_count = hung_counts_per_thread_type[i];
const HangWatcher::ThreadType thread_type =
static_cast<HangWatcher::ThreadType>(i);
if (hang_count != kInvalidHangCount) {
if (hang_count > 0) {
any_thread_hung = true;
}
if (ThreadTypeLoggingLevelGreaterOrEqual(thread_type,
LoggingLevel::kUmaOnly)) {
LogStatusHistogram(thread_type, hang_count, now, monitoring_period);
if (hang_count > 0 && thread_type != ThreadType::kThreadPoolThread) {
any_critical_thread_hung = true;
}
}
}
}
UMA_HISTOGRAM_BOOLEAN("HangWatcher.IsThreadHung.Any", any_thread_hung);
UMA_HISTOGRAM_BOOLEAN("HangWatcher.IsThreadHung.AnyCritical",
any_critical_thread_hung);
if (!all_threads_marked || found_deadline_before_ignore_threshold ||
!any_hung_thread_has_dumping_enabled) {
hung_watch_state_copies_.clear();
return;
}
std::ranges::sort(hung_watch_state_copies_,
[](const WatchStateCopy& lhs, const WatchStateCopy& rhs) {
return lhs.deadline < rhs.deadline;
});
}
void HangWatcher::WatchStateSnapShot::Clear() {
hung_watch_state_copies_.clear();
initialized_ = false;
}
HangWatcher::WatchStateSnapShot::WatchStateSnapShot(
const WatchStateSnapShot& other) = default;
HangWatcher::WatchStateSnapShot::~WatchStateSnapShot() = default;
std::string HangWatcher::WatchStateSnapShot::PrepareHungThreadListCrashKey()
const {
DCHECK(IsActionable());
constexpr char kSeparator{'|'};
std::string list_of_hung_thread_ids;
for (const WatchStateCopy& copy : hung_watch_state_copies_) {
std::string fragment =
base::NumberToString(copy.thread_id.raw()) + kSeparator;
if (list_of_hung_thread_ids.size() + fragment.size() <
static_cast<std::size_t>(debug::CrashKeySize::Size256)) {
list_of_hung_thread_ids += fragment;
} else {
break;
}
}
return list_of_hung_thread_ids;
}
bool HangWatcher::WatchStateSnapShot::IsActionable() const {
DCHECK(initialized_);
return !hung_watch_state_copies_.empty();
}
void HangWatcher::Monitor() {
DCHECK_CALLED_ON_VALID_THREAD(hang_watcher_thread_checker_);
AutoLock auto_lock(watch_state_lock_);
if (watch_states_.empty()) {
return;
}
watch_state_snapshot_.Init(watch_states_, deadline_ignore_threshold_,
monitoring_period_);
if (watch_state_snapshot_.IsActionable()) {
DoDumpWithoutCrashing(watch_state_snapshot_);
}
watch_state_snapshot_.Clear();
}
void HangWatcher::DoDumpWithoutCrashing(
const WatchStateSnapShot& watch_state_snapshot) {
TRACE_EVENT("latency", "HangWatcher::DoDumpWithoutCrashing");
capture_in_progress_.store(true, std::memory_order_relaxed);
base::AutoLock scope_lock(capture_lock_);
const std::string list_of_hung_thread_ids =
watch_state_snapshot.PrepareHungThreadListCrashKey();
static debug::CrashKeyString* crash_key = AllocateCrashKeyString(
"list-of-hung-threads", debug::CrashKeySize::Size256);
const debug::ScopedCrashKeyString list_of_hung_threads_crash_key_string(
crash_key, list_of_hung_thread_ids);
const debug::ScopedCrashKeyString
time_since_last_critical_memory_pressure_crash_key_string =
GetTimeSinceLastCriticalMemoryPressureCrashKey();
SCOPED_CRASH_KEY_STRING32("HangWatcher", "seconds-since-last-resume",
GetTimeSinceLastSystemPowerResumeCrashKeyValue());
SCOPED_CRASH_KEY_BOOL("HangWatcher", "shutting-down",
g_shutting_down.load(std::memory_order_relaxed));
base::TimeTicks latest_expired_deadline =
watch_state_snapshot.GetHighestDeadline();
if (on_hang_closure_for_testing_) {
on_hang_closure_for_testing_.Run();
} else {
RecordHang();
}
deadline_ignore_threshold_ = latest_expired_deadline;
capture_in_progress_.store(false, std::memory_order_relaxed);
}
void HangWatcher::SetAfterMonitorClosureForTesting(
base::RepeatingClosure closure) {
DCHECK_CALLED_ON_VALID_THREAD(constructing_thread_checker_);
after_monitor_closure_for_testing_ = std::move(closure);
}
void HangWatcher::SetOnHangClosureForTesting(base::RepeatingClosure closure) {
DCHECK_CALLED_ON_VALID_THREAD(constructing_thread_checker_);
on_hang_closure_for_testing_ = std::move(closure);
}
void HangWatcher::SetMonitoringPeriodForTesting(base::TimeDelta period) {
DCHECK_CALLED_ON_VALID_THREAD(constructing_thread_checker_);
monitoring_period_ = period;
}
void HangWatcher::SetAfterWaitCallbackForTesting(
RepeatingCallback<void(TimeTicks)> callback) {
DCHECK_CALLED_ON_VALID_THREAD(constructing_thread_checker_);
after_wait_callback_ = callback;
}
void HangWatcher::SignalMonitorEventForTesting() {
DCHECK_CALLED_ON_VALID_THREAD(constructing_thread_checker_);
should_monitor_.Signal();
}
void HangWatcher::StopMonitoringForTesting() {
g_keep_monitoring.store(false, std::memory_order_relaxed);
}
void HangWatcher::SetTickClockForTesting(const base::TickClock* tick_clock) {
tick_clock_ = tick_clock;
}
std::string HangWatcher::GetHungThreadListCrashKeyForTesting() const {
WatchStateSnapShot snapshot;
snapshot.Init(watch_states_, deadline_ignore_threshold_, TimeDelta());
return snapshot.PrepareHungThreadListCrashKey();
}
void HangWatcher::BlockIfCaptureInProgress() {
if (capture_in_progress_.load(std::memory_order_relaxed)) {
base::AutoLock hang_lock(capture_lock_);
}
}
void HangWatcher::UnregisterThread() {
AutoLock auto_lock(watch_state_lock_);
auto it = std::ranges::find(
watch_states_,
internal::HangWatchState::GetHangWatchStateForCurrentThread(),
&std::unique_ptr<internal::HangWatchState>::get);
CHECK(it != watch_states_.end());
watch_states_.erase(it);
}
namespace internal {
namespace {
constexpr uint64_t kOnlyDeadlineMask = 0x00FF'FFFF'FFFF'FFFFu;
constexpr uint64_t kOnlyFlagsMask = ~kOnlyDeadlineMask;
constexpr uint64_t kMaximumFlag = 0x8000'0000'0000'0000u;
constexpr uint64_t kPersistentFlagsAndDeadlineMask =
kOnlyDeadlineMask |
static_cast<uint64_t>(
HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope);
}
static_assert(
static_cast<uint64_t>(HangWatchDeadline::Flag::kMinValue) >
kOnlyDeadlineMask,
"Invalid numerical value for flag. Would interfere with bits of data.");
static_assert(static_cast<uint64_t>(HangWatchDeadline::Flag::kMaxValue) <=
kMaximumFlag,
"A flag can only set a single bit.");
HangWatchDeadline::HangWatchDeadline() = default;
HangWatchDeadline::~HangWatchDeadline() = default;
std::pair<uint64_t, TimeTicks> HangWatchDeadline::GetFlagsAndDeadline() const {
uint64_t bits = bits_.load(std::memory_order_relaxed);
return std::make_pair(ExtractFlags(bits),
DeadlineFromBits(ExtractDeadline((bits))));
}
TimeTicks HangWatchDeadline::GetDeadline() const {
return DeadlineFromBits(
ExtractDeadline(bits_.load(std::memory_order_relaxed)));
}
TimeTicks HangWatchDeadline::Max() {
return DeadlineFromBits(kOnlyDeadlineMask);
}
bool HangWatchDeadline::IsFlagSet(Flag flag, uint64_t flags) {
return static_cast<uint64_t>(flag) & flags;
}
void HangWatchDeadline::SetDeadline(TimeTicks new_deadline) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(new_deadline <= Max()) << "Value too high to be represented.";
DCHECK(new_deadline >= TimeTicks{}) << "Value cannot be negative.";
if (switch_bits_callback_for_testing_) {
const uint64_t switched_in_bits = SwitchBitsForTesting();
DCHECK((switched_in_bits & kPersistentFlagsAndDeadlineMask) == 0u);
}
const uint64_t old_bits = bits_.load(std::memory_order_relaxed);
const uint64_t new_flags =
ExtractFlags(old_bits & kPersistentFlagsAndDeadlineMask);
bits_.store(new_flags | ExtractDeadline(static_cast<uint64_t>(
new_deadline.ToInternalValue())),
std::memory_order_relaxed);
}
bool HangWatchDeadline::SetShouldBlockOnHang(uint64_t old_flags,
TimeTicks old_deadline) {
DCHECK(old_deadline <= Max()) << "Value too high to be represented.";
DCHECK(old_deadline >= TimeTicks{}) << "Value cannot be negative.";
uint64_t old_bits =
old_flags | static_cast<uint64_t>(old_deadline.ToInternalValue());
const uint64_t desired_bits =
old_bits | static_cast<uint64_t>(Flag::kShouldBlockOnHang);
if (switch_bits_callback_for_testing_) {
const uint64_t switched_in_bits = SwitchBitsForTesting();
DCHECK(!IsFlagSet(Flag::kShouldBlockOnHang, switched_in_bits));
}
return bits_.compare_exchange_weak(old_bits, desired_bits,
std::memory_order_relaxed,
std::memory_order_relaxed);
}
void HangWatchDeadline::SetIgnoreCurrentWatchHangsInScope() {
SetPersistentFlag(Flag::kIgnoreCurrentWatchHangsInScope);
}
void HangWatchDeadline::UnsetIgnoreCurrentWatchHangsInScope() {
ClearPersistentFlag(Flag::kIgnoreCurrentWatchHangsInScope);
}
void HangWatchDeadline::SetPersistentFlag(Flag flag) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (switch_bits_callback_for_testing_) {
SwitchBitsForTesting();
}
bits_.fetch_or(static_cast<uint64_t>(flag), std::memory_order_relaxed);
}
void HangWatchDeadline::ClearPersistentFlag(Flag flag) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (switch_bits_callback_for_testing_) {
SwitchBitsForTesting();
}
bits_.fetch_and(~(static_cast<uint64_t>(flag)), std::memory_order_relaxed);
}
uint64_t HangWatchDeadline::ExtractFlags(uint64_t bits) {
return bits & kOnlyFlagsMask;
}
uint64_t HangWatchDeadline::ExtractDeadline(uint64_t bits) {
return bits & kOnlyDeadlineMask;
}
TimeTicks HangWatchDeadline::DeadlineFromBits(uint64_t bits) {
DCHECK(bits <= kOnlyDeadlineMask)
<< "Flags bits are set. Remove them before returning deadline.";
static_assert(kOnlyDeadlineMask <= std::numeric_limits<int64_t>::max());
return TimeTicks::FromInternalValue(static_cast<int64_t>(bits));
}
bool HangWatchDeadline::IsFlagSet(Flag flag) const {
return bits_.load(std::memory_order_relaxed) & static_cast<uint64_t>(flag);
}
void HangWatchDeadline::SetSwitchBitsClosureForTesting(
RepeatingCallback<uint64_t(void)> closure) {
switch_bits_callback_for_testing_ = closure;
}
void HangWatchDeadline::ResetSwitchBitsClosureForTesting() {
DCHECK(switch_bits_callback_for_testing_);
switch_bits_callback_for_testing_.Reset();
}
uint64_t HangWatchDeadline::SwitchBitsForTesting() {
DCHECK(switch_bits_callback_for_testing_);
const uint64_t old_bits = bits_.load(std::memory_order_relaxed);
const uint64_t new_bits = switch_bits_callback_for_testing_.Run();
const uint64_t old_flags = ExtractFlags(old_bits);
const uint64_t switched_in_bits = old_flags | new_bits;
bits_.store(switched_in_bits, std::memory_order_relaxed);
return switched_in_bits;
}
HangWatchState::HangWatchState(HangWatcher::ThreadType thread_type)
: resetter_(&hang_watch_state, this, nullptr), thread_type_(thread_type) {
thread_id_ = PlatformThread::CurrentId();
}
HangWatchState::~HangWatchState() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_EQ(GetHangWatchStateForCurrentThread(), this);
#if DCHECK_IS_ON()
DCHECK(!current_watch_hangs_in_scope_);
#endif
}
std::unique_ptr<HangWatchState>
HangWatchState::CreateHangWatchStateForCurrentThread(
HangWatcher::ThreadType thread_type) {
std::unique_ptr<HangWatchState> hang_state =
std::make_unique<HangWatchState>(thread_type);
DCHECK_EQ(GetHangWatchStateForCurrentThread(), hang_state.get());
return hang_state;
}
TimeTicks HangWatchState::GetDeadline() const {
return deadline_.GetDeadline();
}
std::pair<uint64_t, TimeTicks> HangWatchState::GetFlagsAndDeadline() const {
return deadline_.GetFlagsAndDeadline();
}
void HangWatchState::SetDeadline(TimeTicks deadline) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
deadline_.SetDeadline(deadline);
}
bool HangWatchState::IsOverDeadline() const {
return TimeTicks::Now() > deadline_.GetDeadline();
}
void HangWatchState::SetIgnoreCurrentWatchHangsInScope() {
deadline_.SetIgnoreCurrentWatchHangsInScope();
}
void HangWatchState::UnsetIgnoreCurrentWatchHangsInScope() {
deadline_.UnsetIgnoreCurrentWatchHangsInScope();
}
bool HangWatchState::SetShouldBlockOnHang(uint64_t old_flags,
TimeTicks old_deadline) {
return deadline_.SetShouldBlockOnHang(old_flags, old_deadline);
}
bool HangWatchState::IsFlagSet(HangWatchDeadline::Flag flag) {
return deadline_.IsFlagSet(flag);
}
#if DCHECK_IS_ON()
void HangWatchState::SetCurrentWatchHangsInScope(
WatchHangsInScope* current_hang_watch_scope_enable) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
current_watch_hangs_in_scope_ = current_hang_watch_scope_enable;
}
WatchHangsInScope* HangWatchState::GetCurrentWatchHangsInScope() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return current_watch_hangs_in_scope_;
}
#endif
HangWatchDeadline* HangWatchState::GetHangWatchDeadlineForTesting() {
return &deadline_;
}
void HangWatchState::IncrementNestingLevel() {
++nesting_level_;
}
void HangWatchState::DecrementNestingLevel() {
--nesting_level_;
}
HangWatchState* HangWatchState::GetHangWatchStateForCurrentThread() {
MSAN_UNPOISON(&hang_watch_state, sizeof(internal::HangWatchState*));
return hang_watch_state;
}
PlatformThreadId HangWatchState::GetThreadID() const {
return thread_id_;
}
}
}