// 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 "base/bind.h" #include "base/location.h" #include "base/synchronization/waitable_event.h" #include "base/task/post_task.h" #include "base/test/metrics/histogram_tester.h" #include "base/test/task_environment.h" #include "base/threading/thread.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/abseil-cpp/absl/types/optional.h" using testing::ElementsAre; using testing::Test; using HangAction = media::AudioThreadHangMonitor::HangAction; namespace media { namespace { constexpr int kStarted = static_cast(AudioThreadHangMonitor::ThreadStatus::kStarted); constexpr int kHung = static_cast(AudioThreadHangMonitor::ThreadStatus::kHung); constexpr int kRecovered = static_cast(AudioThreadHangMonitor::ThreadStatus::kRecovered); constexpr base::TimeDelta kShortHangDeadline = base::TimeDelta::FromSeconds(5); constexpr base::TimeDelta kLongHangDeadline = base::TimeDelta::FromMinutes(30); } // namespace class AudioThreadHangMonitorTest : public Test { public: AudioThreadHangMonitorTest() : task_env_(base::test::TaskEnvironment::TimeSource::MOCK_TIME), histograms_(), audio_thread_("Audio thread"), hang_monitor_({nullptr, base::OnTaskRunnerDeleter(nullptr)}) { CHECK(audio_thread_.Start()); // We must inject the main thread task runner as the hang monitor task // runner since TaskEnvironment::FastForwardBy only works for the main // thread. hang_monitor_ = AudioThreadHangMonitor::Create( HangAction::kDoNothing, absl::nullopt, task_env_.GetMockTickClock(), audio_thread_.task_runner(), task_env_.GetMainThreadTaskRunner()); } ~AudioThreadHangMonitorTest() override { hang_monitor_.reset(); task_env_.RunUntilIdle(); } void SetHangActionCallbacksForTesting() { hang_monitor_->SetHangActionCallbacksForTesting( base::BindRepeating(&AudioThreadHangMonitorTest::HangActionDump, base::Unretained(this)), base::BindRepeating(&AudioThreadHangMonitorTest::HangActionTerminate, base::Unretained(this))); } void RunUntilIdle() { task_env_.RunUntilIdle(); } void FlushAudioThread() { base::WaitableEvent ev; audio_thread_.task_runner()->PostTask( FROM_HERE, base::BindOnce(&base::WaitableEvent::Signal, base::Unretained(&ev))); ev.Wait(); } void BlockAudioThreadUntilEvent() { // We keep |event_| as a member of the test fixture to make sure that the // audio thread terminates before |event_| is destructed. event_.Reset(); audio_thread_.task_runner()->PostTask( FROM_HERE, base::BindOnce(&base::WaitableEvent::Wait, base::Unretained(&event_))); } MOCK_METHOD0(HangActionDump, void()); MOCK_METHOD0(HangActionTerminate, void()); base::WaitableEvent event_; base::test::TaskEnvironment task_env_; base::HistogramTester histograms_; base::Thread audio_thread_; AudioThreadHangMonitor::Ptr hang_monitor_; }; TEST_F(AudioThreadHangMonitorTest, LogsThreadStarted) { RunUntilIdle(); EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"), ElementsAre(base::Bucket(kStarted, 1))); } TEST_F(AudioThreadHangMonitorTest, DoesNotLogThreadHungWhenOk) { RunUntilIdle(); for (int i = 0; i < 10; ++i) { // Flush the audio thread, then advance the clock. The audio thread should // register as "alive" every time. FlushAudioThread(); task_env_.FastForwardBy(base::TimeDelta::FromMinutes(1)); } EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"), ElementsAre(base::Bucket(kStarted, 1))); } TEST_F(AudioThreadHangMonitorTest, LogsHungWhenAudioThreadIsBlocked) { RunUntilIdle(); BlockAudioThreadUntilEvent(); task_env_.FastForwardBy(base::TimeDelta::FromMinutes(10)); event_.Signal(); EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"), ElementsAre(base::Bucket(kStarted, 1), base::Bucket(kHung, 1))); } TEST_F(AudioThreadHangMonitorTest, DoesNotLogThreadHungWithShortDeadline) { hang_monitor_ = AudioThreadHangMonitor::Create( HangAction::kDoNothing, kShortHangDeadline, task_env_.GetMockTickClock(), audio_thread_.task_runner(), task_env_.GetMainThreadTaskRunner()); RunUntilIdle(); BlockAudioThreadUntilEvent(); task_env_.FastForwardBy(kShortHangDeadline / 2); event_.Signal(); // Two started events, one for the originally created hang monitor and one for // the new created here. EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"), ElementsAre(base::Bucket(kStarted, 2))); } TEST_F(AudioThreadHangMonitorTest, LogsThreadHungWithShortDeadline) { hang_monitor_ = AudioThreadHangMonitor::Create( HangAction::kDoNothing, kShortHangDeadline, task_env_.GetMockTickClock(), audio_thread_.task_runner(), task_env_.GetMainThreadTaskRunner()); RunUntilIdle(); BlockAudioThreadUntilEvent(); task_env_.FastForwardBy(kShortHangDeadline * 2); event_.Signal(); // Two started events, one for the originally created hang monitor and one for // the new created here. EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"), ElementsAre(base::Bucket(kStarted, 2), base::Bucket(kHung, 1))); } TEST_F(AudioThreadHangMonitorTest, DoesNotLogThreadHungWithLongDeadline) { hang_monitor_ = AudioThreadHangMonitor::Create( HangAction::kDoNothing, kLongHangDeadline, task_env_.GetMockTickClock(), audio_thread_.task_runner(), task_env_.GetMainThreadTaskRunner()); RunUntilIdle(); BlockAudioThreadUntilEvent(); task_env_.FastForwardBy(kLongHangDeadline / 2); event_.Signal(); // Two started events, one for the originally created hang monitor and one for // the new created here. EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"), ElementsAre(base::Bucket(kStarted, 2))); } TEST_F(AudioThreadHangMonitorTest, LogsThreadHungWithLongDeadline) { hang_monitor_ = AudioThreadHangMonitor::Create( HangAction::kDoNothing, kLongHangDeadline, task_env_.GetMockTickClock(), audio_thread_.task_runner(), task_env_.GetMainThreadTaskRunner()); RunUntilIdle(); BlockAudioThreadUntilEvent(); task_env_.FastForwardBy(kLongHangDeadline * 2); event_.Signal(); // Two started events, one for the originally created hang monitor and one for // the new created here. EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"), ElementsAre(base::Bucket(kStarted, 2), base::Bucket(kHung, 1))); } // Zero deadline means that the default deadline should be used. TEST_F(AudioThreadHangMonitorTest, ZeroDeadlineMeansDefaultDeadline) { hang_monitor_ = AudioThreadHangMonitor::Create( HangAction::kDoNothing, base::TimeDelta(), task_env_.GetMockTickClock(), audio_thread_.task_runner(), task_env_.GetMainThreadTaskRunner()); RunUntilIdle(); for (int i = 0; i < 10; ++i) { // Flush the audio thread, then advance the clock. The audio thread should // register as "alive" every time. FlushAudioThread(); task_env_.FastForwardBy(base::TimeDelta::FromMinutes(1)); } EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"), ElementsAre(base::Bucket(kStarted, 2))); BlockAudioThreadUntilEvent(); task_env_.FastForwardBy(base::TimeDelta::FromMinutes(10)); event_.Signal(); EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"), ElementsAre(base::Bucket(kStarted, 2), base::Bucket(kHung, 1))); } TEST_F(AudioThreadHangMonitorTest, LogsRecoveredWhenAudioThreadIsBlockedThenRecovers) { RunUntilIdle(); BlockAudioThreadUntilEvent(); task_env_.FastForwardBy(base::TimeDelta::FromMinutes(10)); event_.Signal(); for (int i = 0; i < 10; ++i) { // Flush the audio thread, then advance the clock. The audio thread should // register as "alive" every time. FlushAudioThread(); task_env_.FastForwardBy(base::TimeDelta::FromMinutes(1)); } EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"), ElementsAre(base::Bucket(kStarted, 1), base::Bucket(kHung, 1), base::Bucket(kRecovered, 1))); } TEST_F(AudioThreadHangMonitorTest, NoHangActionWhenOk) { SetHangActionCallbacksForTesting(); RunUntilIdle(); for (int i = 0; i < 10; ++i) { // Flush the audio thread, then advance the clock. The audio thread should // register as "alive" every time. FlushAudioThread(); task_env_.FastForwardBy(base::TimeDelta::FromMinutes(1)); } EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"), ElementsAre(base::Bucket(kStarted, 1))); } TEST_F(AudioThreadHangMonitorTest, DumpsWhenAudioThreadIsBlocked) { hang_monitor_ = AudioThreadHangMonitor::Create( HangAction::kDump, absl::nullopt, task_env_.GetMockTickClock(), audio_thread_.task_runner(), task_env_.GetMainThreadTaskRunner()); SetHangActionCallbacksForTesting(); RunUntilIdle(); EXPECT_CALL(*this, HangActionDump).Times(1); BlockAudioThreadUntilEvent(); task_env_.FastForwardBy(base::TimeDelta::FromMinutes(10)); event_.Signal(); EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"), ElementsAre(base::Bucket(kStarted, 2), base::Bucket(kHung, 1))); } TEST_F(AudioThreadHangMonitorTest, TerminatesProcessWhenAudioThreadIsBlocked) { hang_monitor_ = AudioThreadHangMonitor::Create( HangAction::kTerminateCurrentProcess, absl::nullopt, task_env_.GetMockTickClock(), audio_thread_.task_runner(), task_env_.GetMainThreadTaskRunner()); SetHangActionCallbacksForTesting(); RunUntilIdle(); EXPECT_CALL(*this, HangActionTerminate).Times(1); BlockAudioThreadUntilEvent(); task_env_.FastForwardBy(base::TimeDelta::FromMinutes(10)); event_.Signal(); EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"), ElementsAre(base::Bucket(kStarted, 2), base::Bucket(kHung, 1))); } TEST_F(AudioThreadHangMonitorTest, DumpsAndTerminatesProcessWhenAudioThreadIsBlocked) { hang_monitor_ = AudioThreadHangMonitor::Create( HangAction::kDumpAndTerminateCurrentProcess, absl::nullopt, task_env_.GetMockTickClock(), audio_thread_.task_runner(), task_env_.GetMainThreadTaskRunner()); SetHangActionCallbacksForTesting(); RunUntilIdle(); EXPECT_CALL(*this, HangActionDump).Times(1); EXPECT_CALL(*this, HangActionTerminate).Times(1); BlockAudioThreadUntilEvent(); task_env_.FastForwardBy(base::TimeDelta::FromMinutes(10)); event_.Signal(); EXPECT_THAT(histograms_.GetAllSamples("Media.AudioThreadStatus"), ElementsAre(base::Bucket(kStarted, 2), base::Bucket(kHung, 1))); } } // namespace media