blob: f625545468fa52bc992c7c39c9110fffa650f031 [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.
#ifndef MEDIA_AUDIO_AUDIO_THREAD_HANG_MONITOR_H_
#define MEDIA_AUDIO_AUDIO_THREAD_HANG_MONITOR_H_
#include "media/audio/audio_manager.h"
#include <atomic>
#include <memory>
#include "base/callback_forward.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "media/base/media_export.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace base {
class TickClock;
class SingleThreadTaskRunner;
} // namespace base
namespace media {
// This class detects if the audio manager thread is hung. It logs a histogram,
// and can optionally (if |dump_on_hang| is set) upload a crash dump when a hang
// is detected. It runs on a task runner from the task scheduler. It works by
// posting a task to the audio thread every minute and checking that it was
// executed. If three consecutive such pings are missed, the thread is
// considered hung.
class MEDIA_EXPORT AudioThreadHangMonitor final {
public:
using Ptr =
std::unique_ptr<AudioThreadHangMonitor, base::OnTaskRunnerDeleter>;
// These values are histogrammed over time; do not change their ordinal
// values.
enum class ThreadStatus {
// kNone = 0, obsolete.
kStarted = 1,
kHung,
kRecovered,
kMaxValue = kRecovered
};
enum class HangAction {
// Do nothing. (UMA logging is always done.)
kDoNothing,
// A crash dump will be collected the first time the thread is detected as
// hung (note that no actual crashing is involved).
kDump,
// Terminate the current process with exit code 0.
kTerminateCurrentProcess,
// Terminate the current process with exit code 1, which yields a crash
// dump.
kDumpAndTerminateCurrentProcess
};
// |monitor_task_runner| may be set explicitly by tests only. Other callers
// should use the default. If |hang_deadline| is not provided, or if it's
// zero, a default value is used.
static Ptr 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 = nullptr);
AudioThreadHangMonitor(const AudioThreadHangMonitor&) = delete;
AudioThreadHangMonitor& operator=(const AudioThreadHangMonitor&) = delete;
~AudioThreadHangMonitor();
// Thread-safe.
bool IsAudioThreadHung() const;
private:
friend class AudioThreadHangMonitorTest;
class SharedAtomicFlag final
: public base::RefCountedThreadSafe<SharedAtomicFlag> {
public:
SharedAtomicFlag();
std::atomic_bool flag_ = {false};
private:
friend class base::RefCountedThreadSafe<SharedAtomicFlag>;
~SharedAtomicFlag();
};
AudioThreadHangMonitor(
HangAction hang_action,
absl::optional<base::TimeDelta> hang_deadline,
const base::TickClock* clock,
scoped_refptr<base::SingleThreadTaskRunner> audio_thread_task_runner);
void StartTimer();
bool NeverLoggedThreadHung() const;
bool NeverLoggedThreadRecoveredAfterHung() const;
// This function is run by the |timer_|. It checks if the audio thread has
// shown signs of life since the last time it was called (by checking the
// |alive_flag_|) and updates the value of |successful_pings_| and
// |failed_pings_| as appropriate. It also changes the thread status and logs
// its value to a histogram.
void CheckIfAudioThreadIsAlive();
// LogHistogramThreadStatus logs |thread_status_| to a histogram.
void LogHistogramThreadStatus();
// For tests. See below functions.
void SetHangActionCallbacksForTesting(
base::RepeatingClosure dump_callback,
base::RepeatingClosure terminate_process_callback);
// Thin wrapper functions that either executes the default or runs a callback
// set with SetHangActioncallbacksForTesting(), for testing purposes.
void DumpWithoutCrashing();
void TerminateCurrentProcess();
const base::TickClock* const clock_;
// This flag is set to false on the monitor sequence and then set to true on
// the audio thread to indicate that the audio thread is alive.
const scoped_refptr<SharedAtomicFlag> alive_flag_;
// |audio_task_runner_| is the task runner of the audio thread.
const scoped_refptr<base::SingleThreadTaskRunner> audio_task_runner_;
// Which action(s) to take when detected hung thread.
const HangAction hang_action_;
// At which interval to ping and see if the thread is running.
const base::TimeDelta ping_interval_;
// For testing. See DumpWithoutCrashing() and TerminateCurrentProcess().
base::RepeatingClosure dump_callback_;
base::RepeatingClosure terminate_process_callback_;
std::atomic<ThreadStatus> audio_thread_status_ = {ThreadStatus::kStarted};
// All fields below are accessed on |monitor_sequence|.
SEQUENCE_CHECKER(monitor_sequence_);
// Timer to check |alive_flag_| regularly.
base::RepeatingTimer timer_;
// This variable is used to check to detect suspend/resume cycles.
// If a long time has passed since the timer was last fired, it is likely due
// to the machine being suspended. In such a case, we want to avoid falsely
// detecting the audio thread as hung.
base::TimeTicks last_check_time_ = base::TimeTicks();
// |recent_ping_state_| tracks the recent life signs from the audio thread. If
// the most recent ping was successful, the number indicates the number of
// successive successful pings. If the most recent ping was failed, the number
// is the negative of the number of successive failed pings.
int recent_ping_state_ = 0;
};
} // namespace media
#endif // MEDIA_AUDIO_AUDIO_THREAD_HANG_MONITOR_H_