1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
|
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/audio/audio_thread_hang_monitor.h"
#include <algorithm>
#include <utility>
#include "base/debug/dump_without_crashing.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/power_monitor/power_monitor.h"
#include "base/process/process.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/thread_checker.h"
#include "base/time/tick_clock.h"
namespace media {
namespace {
// Maximum number of failed pings to the audio thread allowed. A UMA will be
// recorded once this count is reached. We require at least three failed pings
// before recording to ensure unobservable power events aren't mistakenly
// caught (e.g., the system suspends before a OnSuspend() event can be fired).
constexpr int kMaxFailedPingsCount = 3;
// The default deadline after which we consider the audio thread hung.
constexpr base::TimeDelta kDefaultHangDeadline = base::Minutes(3);
} // namespace
AudioThreadHangMonitor::SharedAtomicFlag::SharedAtomicFlag() {}
AudioThreadHangMonitor::SharedAtomicFlag::~SharedAtomicFlag() {}
// static
AudioThreadHangMonitor::Ptr AudioThreadHangMonitor::Create(
HangAction hang_action,
std::optional<base::TimeDelta> hang_deadline,
const base::TickClock* clock,
scoped_refptr<base::SingleThreadTaskRunner> audio_thread_task_runner,
scoped_refptr<base::SequencedTaskRunner> monitor_task_runner) {
if (!monitor_task_runner)
monitor_task_runner = base::ThreadPool::CreateSequencedTaskRunner({});
auto monitor =
Ptr(new AudioThreadHangMonitor(hang_action, hang_deadline, clock,
std::move(audio_thread_task_runner)),
base::OnTaskRunnerDeleter(monitor_task_runner));
// |monitor| is destroyed on |monitor_task_runner|, so Unretained is safe.
monitor_task_runner->PostTask(
FROM_HERE, base::BindOnce(&AudioThreadHangMonitor::StartTimer,
base::Unretained(monitor.get())));
return monitor;
}
AudioThreadHangMonitor::~AudioThreadHangMonitor() {
DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_);
}
bool AudioThreadHangMonitor::IsAudioThreadHung() const {
return audio_thread_status_ == ThreadStatus::kHung;
}
AudioThreadHangMonitor::AudioThreadHangMonitor(
HangAction hang_action,
std::optional<base::TimeDelta> hang_deadline,
const base::TickClock* clock,
scoped_refptr<base::SingleThreadTaskRunner> audio_thread_task_runner)
: clock_(clock),
alive_flag_(base::MakeRefCounted<SharedAtomicFlag>()),
audio_task_runner_(std::move(audio_thread_task_runner)),
hang_action_(hang_action),
ping_interval_((hang_deadline ? hang_deadline.value().is_zero()
? kDefaultHangDeadline
: hang_deadline.value()
: kDefaultHangDeadline) /
kMaxFailedPingsCount),
timer_(clock_) {
DETACH_FROM_SEQUENCE(monitor_sequence_);
}
void AudioThreadHangMonitor::StartTimer() {
DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_);
// Set the flag to true so that the first run doesn't detect a hang.
alive_flag_->flag_ = true;
last_check_time_ = clock_->NowTicks();
LogHistogramThreadStatus();
// |this| owns |timer_|, so Unretained is safe.
timer_.Start(
FROM_HERE, ping_interval_,
base::BindRepeating(&AudioThreadHangMonitor::CheckIfAudioThreadIsAlive,
base::Unretained(this)));
}
bool AudioThreadHangMonitor::NeverLoggedThreadHung() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_);
return audio_thread_status_ == ThreadStatus::kStarted;
}
bool AudioThreadHangMonitor::NeverLoggedThreadRecoveredAfterHung() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_);
return audio_thread_status_ == ThreadStatus::kHung;
}
void AudioThreadHangMonitor::CheckIfAudioThreadIsAlive() {
DCHECK_CALLED_ON_VALID_SEQUENCE(monitor_sequence_);
const base::TimeDelta time_since_last_check =
clock_->NowTicks() - last_check_time_;
// An unexpected |time_since_last_check| may indicate that the system has been
// in sleep mode, in which case the audio thread may have had insufficient
// time to respond to the ping. In such a case, skip the check for now.
if (time_since_last_check > ping_interval_ + base::Seconds(1))
return;
const bool audio_thread_responded_to_last_ping = alive_flag_->flag_;
if (audio_thread_responded_to_last_ping) {
recent_ping_state_ = std::max(recent_ping_state_, 0) + 1;
// Update the thread status if it was previously hung. Will only log
// "recovered" once for the lifetime of this object.
if (NeverLoggedThreadRecoveredAfterHung() &&
recent_ping_state_ >= kMaxFailedPingsCount) {
// Require just as many successful pings to recover from failure.
audio_thread_status_ = ThreadStatus::kRecovered;
LogHistogramThreadStatus();
}
} else {
recent_ping_state_ = std::min(recent_ping_state_, 0) - 1;
// Update the thread status if it was previously live and has never been
// considered hung before. Will only log "hung" once for the lifetime of
// this object.
if (-recent_ping_state_ >= kMaxFailedPingsCount &&
NeverLoggedThreadHung()) {
LOG(ERROR)
<< "Audio thread hang has been detected. You may need to restart "
"your browser. Please file a bug at https://crbug.com/new";
audio_thread_status_ = ThreadStatus::kHung;
LogHistogramThreadStatus();
if (hang_action_ == HangAction::kDump ||
hang_action_ == HangAction::kDumpAndTerminateCurrentProcess) {
DumpWithoutCrashing();
}
if (hang_action_ == HangAction::kTerminateCurrentProcess ||
hang_action_ == HangAction::kDumpAndTerminateCurrentProcess) {
TerminateCurrentProcess();
}
}
}
alive_flag_->flag_ = false;
last_check_time_ = clock_->NowTicks();
audio_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](scoped_refptr<SharedAtomicFlag> flag) { flag->flag_ = true; },
alive_flag_));
}
void AudioThreadHangMonitor::LogHistogramThreadStatus() {
UMA_HISTOGRAM_ENUMERATION("Media.AudioThreadStatus",
audio_thread_status_.load());
}
void AudioThreadHangMonitor::SetHangActionCallbacksForTesting(
base::RepeatingClosure dump_callback,
base::RepeatingClosure terminate_process_callback) {
dump_callback_ = std::move(dump_callback);
terminate_process_callback_ = std::move(terminate_process_callback);
}
void AudioThreadHangMonitor::DumpWithoutCrashing() {
LOG(ERROR) << "Creating non-crash dump for audio thread hang.";
if (!dump_callback_.is_null())
dump_callback_.Run();
else
base::debug::DumpWithoutCrashing();
}
void AudioThreadHangMonitor::TerminateCurrentProcess() {
LOG(ERROR) << "Terminating process for audio thread hang.";
if (!terminate_process_callback_.is_null())
terminate_process_callback_.Run();
else
base::Process::TerminateCurrentProcessImmediately(1);
}
} // namespace media
|