blob: f69a79b483f1c33d6277486ba59fdcdd72f970f4 [file] [log] [blame]
// Copyright 2018 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 "media/audio/audio_thread_hang_monitor.h"
#include <algorithm>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/debug/dump_without_crashing.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/single_thread_task_runner.h"
#include "base/task/post_task.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,
absl::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,
absl::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