| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/threading/hang_watcher.h" |
| #include <atomic> |
| #include <memory> |
| |
| #include "base/barrier_closure.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/synchronization/lock.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/power_monitor_test.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_timeouts.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/threading/thread_checker.h" |
| #include "base/threading/threading_features.h" |
| #include "base/time/tick_clock.h" |
| #include "base/time/time.h" |
| #include "build/build_config.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::IsEmpty; |
| |
| namespace base { |
| namespace { |
| |
| // Use with a FeatureList to activate crash dumping for threads marked as |
| // threadpool threads. |
| const std::vector<base::test::FeatureRefAndParams> kFeatureAndParams{ |
| {base::kEnableHangWatcher, {{"ui_thread_log_level", "2"}}}}; |
| |
| // Use this value to mark things very far off in the future. Adding this |
| // to TimeTicks::Now() gives a point that will never be reached during the |
| // normal execution of a test. |
| constexpr TimeDelta kVeryLongDelta{base::Days(365)}; |
| |
| // A relatively small time delta to ensure ordering of hung threads list. |
| constexpr TimeDelta kSmallCPUQuantum{base::Milliseconds(1)}; |
| |
| constexpr uint64_t kArbitraryDeadline = 0x0000C0FFEEC0FFEEu; |
| constexpr uint64_t kAllOnes = 0xFFFFFFFFFFFFFFFFu; |
| constexpr uint64_t kAllZeros = 0x0000000000000000u; |
| constexpr uint64_t kOnesThenZeroes = 0xAAAAAAAAAAAAAAAAu; |
| constexpr uint64_t kZeroesThenOnes = 0x5555555555555555u; |
| |
| // Waits on provided WaitableEvent before executing and signals when done. |
| class BlockingThread : public DelegateSimpleThread::Delegate { |
| public: |
| explicit BlockingThread(base::WaitableEvent* unblock_thread, |
| base::TimeDelta timeout) |
| : thread_(this, "BlockingThread"), |
| unblock_thread_(unblock_thread), |
| timeout_(timeout) {} |
| |
| ~BlockingThread() override = default; |
| |
| void Run() override { |
| // (Un)Register the thread here instead of in ctor/dtor so that the action |
| // happens on the right thread. |
| base::ScopedClosureRunner unregister_closure = |
| base::HangWatcher::RegisterThread( |
| base::HangWatcher::ThreadType::kMainThread); |
| |
| WatchHangsInScope scope(timeout_); |
| wait_until_entered_scope_.Signal(); |
| |
| unblock_thread_->Wait(); |
| run_event_.Signal(); |
| } |
| |
| bool IsDone() { return run_event_.IsSignaled(); } |
| |
| void StartAndWaitForScopeEntered() { |
| thread_.Start(); |
| // Block until this thread registered itself for hang watching and has |
| // entered a WatchHangsInScope. |
| wait_until_entered_scope_.Wait(); |
| } |
| |
| void Join() { thread_.Join(); } |
| |
| PlatformThreadId GetId() { return thread_.tid(); } |
| |
| private: |
| base::DelegateSimpleThread thread_; |
| |
| // Will be signaled once the thread is properly registered for watching and |
| // the WatchHangsInScope has been entered. |
| WaitableEvent wait_until_entered_scope_; |
| |
| // Will be signaled once ThreadMain has run. |
| WaitableEvent run_event_; |
| |
| const raw_ptr<base::WaitableEvent> unblock_thread_; |
| |
| base::TimeDelta timeout_; |
| }; |
| |
| class HangWatcherTest : public testing::Test { |
| public: |
| const base::TimeDelta kTimeout = base::Seconds(10); |
| const base::TimeDelta kHangTime = kTimeout + base::Seconds(1); |
| |
| HangWatcherTest() { |
| feature_list_.InitWithFeaturesAndParameters(kFeatureAndParams, {}); |
| hang_watcher_.InitializeOnMainThread( |
| HangWatcher::ProcessType::kBrowserProcess); |
| |
| hang_watcher_.SetAfterMonitorClosureForTesting(base::BindRepeating( |
| &WaitableEvent::Signal, base::Unretained(&monitor_event_))); |
| |
| hang_watcher_.SetOnHangClosureForTesting(base::BindRepeating( |
| &WaitableEvent::Signal, base::Unretained(&hang_event_))); |
| |
| // We're not testing the monitoring loop behavior in this test so we want to |
| // trigger monitoring manually. |
| hang_watcher_.SetMonitoringPeriodForTesting(kVeryLongDelta); |
| |
| // Start the monitoring loop. |
| hang_watcher_.Start(); |
| } |
| |
| void TearDown() override { hang_watcher_.UnitializeOnMainThreadForTesting(); } |
| |
| HangWatcherTest(const HangWatcherTest& other) = delete; |
| HangWatcherTest& operator=(const HangWatcherTest& other) = delete; |
| |
| protected: |
| // Used to wait for monitoring. Will be signaled by the HangWatcher thread and |
| // so needs to outlive it. |
| WaitableEvent monitor_event_; |
| |
| // Signaled from the HangWatcher thread when a hang is detected. Needs to |
| // outlive the HangWatcher thread. |
| WaitableEvent hang_event_; |
| |
| base::test::ScopedFeatureList feature_list_; |
| |
| // Used exclusively for MOCK_TIME. No tasks will be run on the environment. |
| // Single threaded to avoid ThreadPool WorkerThreads registering. |
| test::SingleThreadTaskEnvironment task_environment_{ |
| test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| |
| // This must be declared last (after task_environment_, for example) so that |
| // the watcher thread is joined before objects like the mock timer are |
| // destroyed, causing racy crashes. |
| HangWatcher hang_watcher_; |
| }; |
| |
| class HangWatcherBlockingThreadTest : public HangWatcherTest { |
| public: |
| HangWatcherBlockingThreadTest() : thread_(&unblock_thread_, kTimeout) {} |
| |
| HangWatcherBlockingThreadTest(const HangWatcherBlockingThreadTest& other) = |
| delete; |
| HangWatcherBlockingThreadTest& operator=( |
| const HangWatcherBlockingThreadTest& other) = delete; |
| |
| protected: |
| void JoinThread() { |
| unblock_thread_.Signal(); |
| |
| // Thread is joinable since we signaled |unblock_thread_|. |
| thread_.Join(); |
| |
| // If thread is done then it signaled. |
| ASSERT_TRUE(thread_.IsDone()); |
| } |
| |
| void StartBlockedThread() { |
| // Thread has not run yet. |
| ASSERT_FALSE(thread_.IsDone()); |
| |
| // Start the thread. It will block since |unblock_thread_| was not |
| // signaled yet. |
| thread_.StartAndWaitForScopeEntered(); |
| |
| // Thread registration triggered a call to HangWatcher::Monitor() which |
| // signaled |monitor_event_|. Reset it so it's ready for waiting later on. |
| monitor_event_.Reset(); |
| } |
| |
| void MonitorHangs() { |
| // HangWatcher::Monitor() should not be set which would mean a call to |
| // HangWatcher::Monitor() happened and was unacounted for. |
| // ASSERT_FALSE(monitor_event_.IsSignaled()); |
| |
| // Trigger a monitoring on HangWatcher thread and verify results. |
| hang_watcher_.SignalMonitorEventForTesting(); |
| monitor_event_.Wait(); |
| } |
| |
| // Used to unblock the monitored thread. Signaled from the test main thread. |
| WaitableEvent unblock_thread_; |
| |
| BlockingThread thread_; |
| }; |
| } // namespace |
| |
| TEST_F(HangWatcherTest, InvalidatingExpectationsPreventsCapture) { |
| // Register the main test thread for hang watching. |
| auto unregister_thread_closure = |
| HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread); |
| |
| // Create a hang. |
| WatchHangsInScope expires_instantly(base::TimeDelta{}); |
| task_environment_.FastForwardBy(kHangTime); |
| |
| // de-activate hang watching, |
| base::HangWatcher::InvalidateActiveExpectations(); |
| |
| // Trigger a monitoring on HangWatcher thread and verify results. |
| // Hang is not detected. |
| hang_watcher_.SignalMonitorEventForTesting(); |
| monitor_event_.Wait(); |
| ASSERT_FALSE(hang_event_.IsSignaled()); |
| } |
| |
| TEST_F(HangWatcherTest, MultipleInvalidateExpectationsDoNotCancelOut) { |
| // Register the main test thread for hang watching. |
| auto unregister_thread_closure = |
| HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread); |
| |
| // Create a hang. |
| WatchHangsInScope expires_instantly(base::TimeDelta{}); |
| task_environment_.FastForwardBy(kHangTime); |
| |
| // de-activate hang watching, |
| base::HangWatcher::InvalidateActiveExpectations(); |
| |
| // Redundently de-activate hang watching. |
| base::HangWatcher::InvalidateActiveExpectations(); |
| |
| // Trigger a monitoring on HangWatcher thread and verify results. |
| // Hang is not detected. |
| hang_watcher_.SignalMonitorEventForTesting(); |
| monitor_event_.Wait(); |
| ASSERT_FALSE(hang_event_.IsSignaled()); |
| } |
| |
| TEST_F(HangWatcherTest, NewInnerWatchHangsInScopeAfterInvalidationDetectsHang) { |
| // Register the main test thread for hang watching. |
| auto unregister_thread_closure = |
| HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread); |
| |
| WatchHangsInScope expires_instantly(base::TimeDelta{}); |
| task_environment_.FastForwardBy(kHangTime); |
| |
| // De-activate hang watching. |
| base::HangWatcher::InvalidateActiveExpectations(); |
| |
| { |
| WatchHangsInScope also_expires_instantly(base::TimeDelta{}); |
| task_environment_.FastForwardBy(kHangTime); |
| |
| // Trigger a monitoring on HangWatcher thread and verify results. |
| hang_watcher_.SignalMonitorEventForTesting(); |
| monitor_event_.Wait(); |
| |
| // Hang is detected since the new WatchHangsInScope temporarily |
| // re-activated hang_watching. |
| monitor_event_.Wait(); |
| ASSERT_TRUE(hang_event_.IsSignaled()); |
| } |
| |
| // Reset to attempt capture again. |
| monitor_event_.Reset(); |
| hang_event_.Reset(); |
| |
| // Trigger a monitoring on HangWatcher thread and verify results. |
| hang_watcher_.SignalMonitorEventForTesting(); |
| monitor_event_.Wait(); |
| |
| // Hang is not detected since execution is back to being covered by |
| // |expires_instantly| for which expectations were invalidated. |
| monitor_event_.Wait(); |
| ASSERT_FALSE(hang_event_.IsSignaled()); |
| } |
| |
| TEST_F(HangWatcherTest, |
| NewSeparateWatchHangsInScopeAfterInvalidationDetectsHang) { |
| // Register the main test thread for hang watching. |
| auto unregister_thread_closure = |
| HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread); |
| |
| { |
| WatchHangsInScope expires_instantly(base::TimeDelta{}); |
| task_environment_.FastForwardBy(kHangTime); |
| |
| // De-activate hang watching. |
| base::HangWatcher::InvalidateActiveExpectations(); |
| } |
| |
| WatchHangsInScope also_expires_instantly(base::TimeDelta{}); |
| task_environment_.FastForwardBy(kHangTime); |
| |
| // Trigger a monitoring on HangWatcher thread and verify results. |
| hang_watcher_.SignalMonitorEventForTesting(); |
| monitor_event_.Wait(); |
| |
| // Hang is detected since the new WatchHangsInScope did not have its |
| // expectations invalidated. |
| monitor_event_.Wait(); |
| ASSERT_TRUE(hang_event_.IsSignaled()); |
| } |
| |
| // Test that invalidating expectations from inner WatchHangsInScope will also |
| // prevent hang detection in outer scopes. |
| TEST_F(HangWatcherTest, ScopeDisabledObjectInnerScope) { |
| // Register the main test thread for hang watching. |
| auto unregister_thread_closure = |
| HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread); |
| |
| // Start a WatchHangsInScope that expires right away. Then advance |
| // time to make sure no hang is detected. |
| WatchHangsInScope expires_instantly(base::TimeDelta{}); |
| task_environment_.FastForwardBy(kHangTime); |
| { |
| WatchHangsInScope also_expires_instantly(base::TimeDelta{}); |
| |
| // De-activate hang watching. |
| base::HangWatcher::InvalidateActiveExpectations(); |
| task_environment_.FastForwardBy(kHangTime); |
| } |
| |
| // Trigger a monitoring on HangWatcher thread and verify results. |
| hang_watcher_.SignalMonitorEventForTesting(); |
| monitor_event_.Wait(); |
| |
| // Hang is ignored since it concerns a scope for which one of the inner scope |
| // was ignored. |
| ASSERT_FALSE(hang_event_.IsSignaled()); |
| } |
| |
| TEST_F(HangWatcherTest, NewScopeAfterDisabling) { |
| // Register the main test thread for hang watching. |
| auto unregister_thread_closure = |
| HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread); |
| |
| // Start a WatchHangsInScope that expires right away. Then advance |
| // time to make sure no hang is detected. |
| WatchHangsInScope expires_instantly(base::TimeDelta{}); |
| task_environment_.FastForwardBy(kHangTime); |
| { |
| WatchHangsInScope also_expires_instantly(base::TimeDelta{}); |
| |
| // De-activate hang watching. |
| base::HangWatcher::InvalidateActiveExpectations(); |
| task_environment_.FastForwardBy(kHangTime); |
| } |
| |
| // New scope for which expecations are never invalidated. |
| WatchHangsInScope also_expires_instantly(base::TimeDelta{}); |
| task_environment_.FastForwardBy(kHangTime); |
| |
| // Trigger a monitoring on HangWatcher thread and verify results. |
| hang_watcher_.SignalMonitorEventForTesting(); |
| monitor_event_.Wait(); |
| |
| // Hang is detected because it's unrelated to the hangs that were disabled. |
| ASSERT_TRUE(hang_event_.IsSignaled()); |
| } |
| |
| TEST_F(HangWatcherTest, NestedScopes) { |
| // Create a state object for the test thread since this test is single |
| // threaded. |
| auto current_hang_watch_state = |
| base::internal::HangWatchState::CreateHangWatchStateForCurrentThread( |
| HangWatcher::ThreadType::kMainThread); |
| |
| ASSERT_FALSE(current_hang_watch_state->IsOverDeadline()); |
| base::TimeTicks original_deadline = current_hang_watch_state->GetDeadline(); |
| |
| constexpr base::TimeDelta kFirstTimeout(base::Milliseconds(500)); |
| base::TimeTicks first_deadline = base::TimeTicks::Now() + kFirstTimeout; |
| |
| constexpr base::TimeDelta kSecondTimeout(base::Milliseconds(250)); |
| base::TimeTicks second_deadline = base::TimeTicks::Now() + kSecondTimeout; |
| |
| // At this point we have not set any timeouts. |
| { |
| // Create a first timeout which is more restrictive than the default. |
| WatchHangsInScope first_scope(kFirstTimeout); |
| |
| // We are on mock time. There is no time advancement and as such no hangs. |
| ASSERT_FALSE(current_hang_watch_state->IsOverDeadline()); |
| ASSERT_EQ(current_hang_watch_state->GetDeadline(), first_deadline); |
| { |
| // Set a yet more restrictive deadline. Still no hang. |
| WatchHangsInScope second_scope(kSecondTimeout); |
| ASSERT_FALSE(current_hang_watch_state->IsOverDeadline()); |
| ASSERT_EQ(current_hang_watch_state->GetDeadline(), second_deadline); |
| } |
| // First deadline we set should be restored. |
| ASSERT_FALSE(current_hang_watch_state->IsOverDeadline()); |
| ASSERT_EQ(current_hang_watch_state->GetDeadline(), first_deadline); |
| } |
| |
| // Original deadline should now be restored. |
| ASSERT_FALSE(current_hang_watch_state->IsOverDeadline()); |
| ASSERT_EQ(current_hang_watch_state->GetDeadline(), original_deadline); |
| } |
| |
| TEST_F(HangWatcherBlockingThreadTest, HistogramsLoggedOnHang) { |
| base::HistogramTester histogram_tester; |
| StartBlockedThread(); |
| |
| // Simulate hang. |
| task_environment_.FastForwardBy(kHangTime); |
| |
| // First monitoring catches the hang and emits the histogram. |
| MonitorHangs(); |
| EXPECT_THAT(histogram_tester.GetAllSamples("HangWatcher.IsThreadHung." |
| "BrowserProcess.UIThread"), |
| ElementsAre(base::Bucket(true, /*count=*/1))); |
| |
| // Reset to attempt capture again. |
| hang_event_.Reset(); |
| monitor_event_.Reset(); |
| |
| // Hang is logged again even if it would not trigger a crash dump. |
| MonitorHangs(); |
| EXPECT_THAT(histogram_tester.GetAllSamples("HangWatcher.IsThreadHung." |
| "BrowserProcess.UIThread"), |
| ElementsAre(base::Bucket(true, /*count=*/2))); |
| |
| // Thread types that are not monitored should not get any samples. |
| EXPECT_THAT(histogram_tester.GetAllSamples("HangWatcher.IsThreadHung." |
| "BrowserProcess.IOThread"), |
| IsEmpty()); |
| JoinThread(); |
| } |
| |
| TEST_F(HangWatcherBlockingThreadTest, HistogramsLoggedWithoutHangs) { |
| base::HistogramTester histogram_tester; |
| StartBlockedThread(); |
| |
| // No hang to catch so nothing is recorded. |
| MonitorHangs(); |
| ASSERT_FALSE(hang_event_.IsSignaled()); |
| |
| // A thread of type ThreadForTesting was monitored but didn't hang. This is |
| // logged. |
| EXPECT_THAT(histogram_tester.GetAllSamples("HangWatcher.IsThreadHung." |
| "BrowserProcess.UIThread"), |
| ElementsAre(base::Bucket(false, /*count=*/1))); |
| |
| // Thread types that are not monitored should not get any samples. |
| EXPECT_THAT(histogram_tester.GetAllSamples("HangWatcher.IsThreadHung." |
| "BrowserProcess.IOThread"), |
| IsEmpty()); |
| JoinThread(); |
| } |
| |
| TEST_F(HangWatcherBlockingThreadTest, Hang) { |
| StartBlockedThread(); |
| |
| // Simulate hang. |
| task_environment_.FastForwardBy(kHangTime); |
| |
| // First monitoring catches and records the hang. |
| MonitorHangs(); |
| ASSERT_TRUE(hang_event_.IsSignaled()); |
| |
| JoinThread(); |
| } |
| |
| TEST_F(HangWatcherBlockingThreadTest, HangAlreadyRecorded) { |
| StartBlockedThread(); |
| |
| // Simulate hang. |
| task_environment_.FastForwardBy(kHangTime); |
| |
| // First monitoring catches and records the hang. |
| MonitorHangs(); |
| ASSERT_TRUE(hang_event_.IsSignaled()); |
| |
| // Reset to attempt capture again. |
| hang_event_.Reset(); |
| monitor_event_.Reset(); |
| |
| // Second monitoring does not record because a hang that was already recorded |
| // is still live. |
| MonitorHangs(); |
| ASSERT_FALSE(hang_event_.IsSignaled()); |
| |
| JoinThread(); |
| } |
| |
| TEST_F(HangWatcherBlockingThreadTest, NoHang) { |
| StartBlockedThread(); |
| |
| // No hang to catch so nothing is recorded. |
| MonitorHangs(); |
| ASSERT_FALSE(hang_event_.IsSignaled()); |
| |
| JoinThread(); |
| } |
| |
| namespace { |
| class HangWatcherSnapshotTest : public testing::Test { |
| public: |
| void SetUp() override { |
| feature_list_.InitWithFeaturesAndParameters(kFeatureAndParams, {}); |
| hang_watcher_.InitializeOnMainThread( |
| HangWatcher::ProcessType::kBrowserProcess); |
| |
| // The monitoring loop behavior is not verified in this test so we want to |
| // trigger monitoring manually. |
| hang_watcher_.SetMonitoringPeriodForTesting(kVeryLongDelta); |
| } |
| |
| void TearDown() override { hang_watcher_.UnitializeOnMainThreadForTesting(); } |
| |
| HangWatcherSnapshotTest() = default; |
| HangWatcherSnapshotTest(const HangWatcherSnapshotTest& other) = delete; |
| HangWatcherSnapshotTest& operator=(const HangWatcherSnapshotTest& other) = |
| delete; |
| |
| protected: |
| void TriggerMonitorAndWaitForCompletion() { |
| monitor_event_.Reset(); |
| hang_watcher_.SignalMonitorEventForTesting(); |
| monitor_event_.Wait(); |
| } |
| |
| // Verify that a capture takes place and that at the time of the capture the |
| // list of hung thread ids is correct. |
| void TestIDList(const std::string& id_list) { |
| list_of_hung_thread_ids_during_capture_ = id_list; |
| task_environment_.AdvanceClock(kSmallCPUQuantum); |
| TriggerMonitorAndWaitForCompletion(); |
| ASSERT_EQ(++reference_capture_count_, hang_capture_count_); |
| } |
| |
| // Verify that even if hang monitoring takes place no hangs are detected. |
| void ExpectNoCapture() { |
| int old_capture_count = hang_capture_count_; |
| task_environment_.AdvanceClock(kSmallCPUQuantum); |
| TriggerMonitorAndWaitForCompletion(); |
| ASSERT_EQ(old_capture_count, hang_capture_count_); |
| } |
| |
| std::string ConcatenateThreadIds( |
| const std::vector<base::PlatformThreadId>& ids) const { |
| std::string result; |
| constexpr char kSeparator{'|'}; |
| |
| for (PlatformThreadId id : ids) { |
| result += base::NumberToString(id) + kSeparator; |
| } |
| |
| return result; |
| } |
| |
| // Will be signaled once monitoring took place. Marks the end of the test. |
| WaitableEvent monitor_event_; |
| |
| const PlatformThreadId test_thread_id_ = PlatformThread::CurrentId(); |
| |
| // This is written to by the test main thread and read from the hang watching |
| // thread. It does not need to be protected because access to it is |
| // synchronized by always setting before triggering the execution of the |
| // reading code through HangWatcher::SignalMonitorEventForTesting(). |
| std::string list_of_hung_thread_ids_during_capture_; |
| |
| // This is written to by from the hang watching thread and read the test main |
| // thread. It does not need to be protected because access to it is |
| // synchronized by always reading after monitor_event_ has been signaled. |
| int hang_capture_count_ = 0; |
| |
| // Increases at the same time as |hang_capture_count_| to test that capture |
| // actually took place. |
| int reference_capture_count_ = 0; |
| |
| std::string seconds_since_last_power_resume_crash_key_; |
| |
| base::test::ScopedFeatureList feature_list_; |
| |
| // Used exclusively for MOCK_TIME. |
| test::SingleThreadTaskEnvironment task_environment_{ |
| test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| |
| HangWatcher hang_watcher_; |
| }; |
| } // namespace |
| |
| // Verify that the hang capture fails when marking a thread for blocking fails. |
| // This simulates a WatchHangsInScope completing between the time the hang |
| // was dected and the time it is recorded which would create a non-actionable |
| // report. |
| TEST_F(HangWatcherSnapshotTest, NonActionableReport) { |
| hang_watcher_.SetOnHangClosureForTesting( |
| base::BindLambdaForTesting([this]() { ++hang_capture_count_; })); |
| hang_watcher_.SetAfterMonitorClosureForTesting( |
| base::BindLambdaForTesting([this]() { monitor_event_.Signal(); })); |
| |
| hang_watcher_.Start(); |
| |
| // Register the main test thread for hang watching. |
| auto unregister_thread_closure = |
| HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread); |
| { |
| // Start a WatchHangsInScope that expires right away. Ensures that |
| // the first monitor will detect a hang. |
| WatchHangsInScope expires_instantly(base::TimeDelta{}); |
| |
| internal::HangWatchState* current_hang_watch_state = |
| internal::HangWatchState::GetHangWatchStateForCurrentThread(); |
| |
| // Simulate the deadline changing concurrently during the capture. This |
| // makes the capture fail since marking of the deadline fails. |
| ASSERT_NE(current_hang_watch_state->GetDeadline(), |
| base::TimeTicks::FromInternalValue(kArbitraryDeadline)); |
| current_hang_watch_state->GetHangWatchDeadlineForTesting() |
| ->SetSwitchBitsClosureForTesting( |
| base::BindLambdaForTesting([]() { return kArbitraryDeadline; })); |
| |
| ExpectNoCapture(); |
| |
| // Marking failed. |
| ASSERT_FALSE(current_hang_watch_state->IsFlagSet( |
| internal::HangWatchDeadline::Flag::kShouldBlockOnHang)); |
| |
| current_hang_watch_state->GetHangWatchDeadlineForTesting() |
| ->ResetSwitchBitsClosureForTesting(); |
| } |
| } |
| |
| // TODO(crbug.com/1223033): On MAC, the base::PlatformThread::CurrentId(...) |
| // should return the system wide IDs. The HungThreadIDs test fails because the |
| // reported process ids do not match. |
| #if BUILDFLAG(IS_MAC) |
| #define MAYBE_HungThreadIDs DISABLED_HungThreadIDs |
| #else |
| #define MAYBE_HungThreadIDs HungThreadIDs |
| #endif |
| |
| TEST_F(HangWatcherSnapshotTest, MAYBE_HungThreadIDs) { |
| // During hang capture the list of hung threads should be populated. |
| hang_watcher_.SetOnHangClosureForTesting(base::BindLambdaForTesting([this]() { |
| EXPECT_EQ(hang_watcher_.GrabWatchStateSnapshotForTesting() |
| .PrepareHungThreadListCrashKey(), |
| list_of_hung_thread_ids_during_capture_); |
| ++hang_capture_count_; |
| })); |
| |
| // When hang capture is over the list should be empty. |
| hang_watcher_.SetAfterMonitorClosureForTesting( |
| base::BindLambdaForTesting([this]() { |
| monitor_event_.Signal(); |
| })); |
| |
| hang_watcher_.Start(); |
| |
| // Register the main test thread for hang watching. |
| auto unregister_thread_closure = |
| HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread); |
| |
| BlockingThread blocking_thread(&monitor_event_, base::TimeDelta{}); |
| blocking_thread.StartAndWaitForScopeEntered(); |
| { |
| // Ensure the blocking thread entered the scope before the main thread. This |
| // will guarantee an ordering while reporting the list of hung threads. |
| task_environment_.AdvanceClock(kSmallCPUQuantum); |
| |
| // Start a WatchHangsInScope that expires right away. Ensures that |
| // the first monitor will detect a hang. This scope will naturally have a |
| // later deadline than the one in |blocking_thread_| since it was created |
| // after. |
| WatchHangsInScope expires_instantly(base::TimeDelta{}); |
| |
| // Hung thread list should contain the id the blocking thread and then the |
| // id of the test main thread since that is the order of increasing |
| // deadline. |
| TestIDList( |
| ConcatenateThreadIds({blocking_thread.GetId(), test_thread_id_})); |
| |
| // |expires_instantly| and the scope from |blocking_thread| are still live |
| // but already recorded so should be ignored. |
| ExpectNoCapture(); |
| |
| // Thread is joinable since we signaled |monitor_event_|. This closes the |
| // scope in |blocking_thread|. |
| blocking_thread.Join(); |
| |
| // |expires_instantly| is still live but already recorded so should be |
| // ignored. |
| ExpectNoCapture(); |
| } |
| |
| // All HangWatchScopeEnables are over. There should be no capture. |
| ExpectNoCapture(); |
| |
| // Once all recorded scopes are over creating a new one and monitoring will |
| // trigger a hang detection. |
| WatchHangsInScope expires_instantly(base::TimeDelta{}); |
| TestIDList(ConcatenateThreadIds({test_thread_id_})); |
| } |
| |
| TEST_F(HangWatcherSnapshotTest, TimeSinceLastSystemPowerResumeCrashKey) { |
| // Override the capture of hangs. Simulate a crash key capture. |
| hang_watcher_.SetOnHangClosureForTesting(base::BindLambdaForTesting([this]() { |
| ++hang_capture_count_; |
| seconds_since_last_power_resume_crash_key_ = |
| hang_watcher_.GetTimeSinceLastSystemPowerResumeCrashKeyValue(); |
| })); |
| |
| // When hang capture is over, unblock the main thread. |
| hang_watcher_.SetAfterMonitorClosureForTesting( |
| base::BindLambdaForTesting([this]() { monitor_event_.Signal(); })); |
| |
| hang_watcher_.Start(); |
| |
| // Register the main test thread for hang watching. |
| auto unregister_thread_closure = |
| HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread); |
| |
| { |
| WatchHangsInScope expires_instantly(base::TimeDelta{}); |
| task_environment_.AdvanceClock(kSmallCPUQuantum); |
| |
| TriggerMonitorAndWaitForCompletion(); |
| EXPECT_EQ(1, hang_capture_count_); |
| EXPECT_EQ("Never suspended", seconds_since_last_power_resume_crash_key_); |
| } |
| |
| { |
| test::ScopedPowerMonitorTestSource power_monitor_source; |
| power_monitor_source.Suspend(); |
| task_environment_.AdvanceClock(kSmallCPUQuantum); |
| |
| { |
| WatchHangsInScope expires_instantly(base::TimeDelta{}); |
| task_environment_.AdvanceClock(kSmallCPUQuantum); |
| TriggerMonitorAndWaitForCompletion(); |
| EXPECT_EQ(2, hang_capture_count_); |
| EXPECT_EQ("Power suspended", seconds_since_last_power_resume_crash_key_); |
| } |
| |
| power_monitor_source.Resume(); |
| constexpr TimeDelta kAfterResumeTime{base::Seconds(5)}; |
| task_environment_.AdvanceClock(kAfterResumeTime); |
| |
| { |
| WatchHangsInScope expires_instantly(base::TimeDelta{}); |
| TriggerMonitorAndWaitForCompletion(); |
| EXPECT_EQ(3, hang_capture_count_); |
| EXPECT_EQ(base::NumberToString(kAfterResumeTime.InSeconds()), |
| seconds_since_last_power_resume_crash_key_); |
| } |
| } |
| } |
| |
| namespace { |
| |
| // Determines how long the HangWatcher will wait between calls to |
| // Monitor(). Choose a low value so that that successive invocations happens |
| // fast. This makes tests that wait for monitoring run fast and makes tests that |
| // expect no monitoring fail fast. |
| const base::TimeDelta kMonitoringPeriod = base::Milliseconds(1); |
| |
| // Test if and how often the HangWatcher periodically monitors for hangs. |
| class HangWatcherPeriodicMonitoringTest : public testing::Test { |
| public: |
| HangWatcherPeriodicMonitoringTest() { |
| hang_watcher_.InitializeOnMainThread( |
| HangWatcher::ProcessType::kBrowserProcess); |
| |
| hang_watcher_.SetMonitoringPeriodForTesting(kMonitoringPeriod); |
| hang_watcher_.SetOnHangClosureForTesting(base::BindRepeating( |
| &WaitableEvent::Signal, base::Unretained(&hang_event_))); |
| |
| // HangWatcher uses a TickClock to detect how long it slept in between calls |
| // to Monitor(). Override that clock to control its subjective passage of |
| // time. |
| hang_watcher_.SetTickClockForTesting(&test_clock_); |
| } |
| |
| HangWatcherPeriodicMonitoringTest( |
| const HangWatcherPeriodicMonitoringTest& other) = delete; |
| HangWatcherPeriodicMonitoringTest& operator=( |
| const HangWatcherPeriodicMonitoringTest& other) = delete; |
| |
| void TearDown() override { hang_watcher_.UnitializeOnMainThreadForTesting(); } |
| |
| protected: |
| // Setup the callback invoked after waiting in HangWatcher to advance the |
| // tick clock by the desired time delta. |
| void InstallAfterWaitCallback(base::TimeDelta time_delta) { |
| hang_watcher_.SetAfterWaitCallbackForTesting(base::BindLambdaForTesting( |
| [this, time_delta](base::TimeTicks time_before_wait) { |
| test_clock_.Advance(time_delta); |
| })); |
| } |
| |
| base::SimpleTestTickClock test_clock_; |
| |
| // Single threaded to avoid ThreadPool WorkerThreads registering. Will run |
| // delayed tasks created by the tests. |
| test::SingleThreadTaskEnvironment task_environment_; |
| |
| std::unique_ptr<base::TickClock> fake_tick_clock_; |
| HangWatcher hang_watcher_; |
| |
| // Signaled when a hang is detected. |
| WaitableEvent hang_event_; |
| |
| base::ScopedClosureRunner unregister_thread_closure_; |
| }; |
| } // namespace |
| |
| // Don't register any threads for hang watching. HangWatcher should not monitor. |
| TEST_F(HangWatcherPeriodicMonitoringTest, |
| NoPeriodicMonitoringWithoutRegisteredThreads) { |
| RunLoop run_loop; |
| |
| // If a call to HangWatcher::Monitor() takes place the test will instantly |
| // fail. |
| hang_watcher_.SetAfterMonitorClosureForTesting( |
| base::BindLambdaForTesting([&run_loop]() { |
| ADD_FAILURE() << "Monitoring took place!"; |
| run_loop.Quit(); |
| })); |
| |
| // Make the HangWatcher tick clock advance by exactly the monitoring period |
| // after waiting so it will never detect oversleeping between attempts to call |
| // Monitor(). This would inhibit monitoring and make the test pass for the |
| // wrong reasons. |
| InstallAfterWaitCallback(kMonitoringPeriod); |
| |
| hang_watcher_.Start(); |
| |
| // Unblock the test thread. No thread ever registered after the HangWatcher |
| // was created in the test's constructor. No monitoring should have taken |
| // place. |
| task_environment_.GetMainThreadTaskRunner()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout()); |
| run_loop.Run(); |
| |
| // NOTE: |
| // A lack of calls could technically also be caused by the HangWatcher thread |
| // executing too slowly / being descheduled. This is a known limitation. |
| // It's expected for |TestTimeouts::tiny_timeout()| to be large enough that |
| // this is rare. |
| } |
| |
| // During normal execution periodic monitorings should take place. |
| TEST_F(HangWatcherPeriodicMonitoringTest, PeriodicCallsTakePlace) { |
| // HangWatcher::Monitor() will run once right away on thread registration. |
| // We want to make sure it runs at a couple more times from being scheduled. |
| constexpr int kMinimumMonitorCount = 3; |
| |
| RunLoop run_loop; |
| |
| // Setup the HangWatcher to unblock run_loop when the Monitor() has been |
| // invoked enough times. |
| hang_watcher_.SetAfterMonitorClosureForTesting(BarrierClosure( |
| kMinimumMonitorCount, base::BindLambdaForTesting([&run_loop]() { |
| // Test condition are confirmed, stop monitoring. |
| HangWatcher::StopMonitoringForTesting(); |
| |
| // Unblock the test main thread. |
| run_loop.Quit(); |
| }))); |
| |
| // Make the HangWatcher tick clock advance by exactly the monitoring period |
| // after waiting so it will never detect oversleeping between attempts to call |
| // Monitor(). This would inhibit monitoring. |
| InstallAfterWaitCallback(kMonitoringPeriod); |
| |
| hang_watcher_.Start(); |
| |
| // Register a thread, |
| unregister_thread_closure_ = |
| HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread); |
| |
| run_loop.Run(); |
| |
| // No monitored scope means no possible hangs. |
| ASSERT_FALSE(hang_event_.IsSignaled()); |
| } |
| |
| // If the HangWatcher detects it slept for longer than expected it will not |
| // monitor. |
| TEST_F(HangWatcherPeriodicMonitoringTest, NoMonitorOnOverSleep) { |
| RunLoop run_loop; |
| |
| // If a call to HangWatcher::Monitor() takes place the test will instantly |
| // fail. |
| hang_watcher_.SetAfterMonitorClosureForTesting( |
| base::BindLambdaForTesting([&run_loop]() { |
| ADD_FAILURE() << "Monitoring took place!"; |
| run_loop.Quit(); |
| })); |
| |
| // Make the HangWatcher tick clock advance so much after waiting that it will |
| // detect oversleeping every time. This will keep it from monitoring. |
| InstallAfterWaitCallback(base::Minutes(1)); |
| |
| hang_watcher_.Start(); |
| |
| // Register a thread. |
| unregister_thread_closure_ = |
| HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread); |
| |
| // Unblock the test thread. All waits were perceived as oversleeping so all |
| // monitoring was inhibited. |
| task_environment_.GetMainThreadTaskRunner()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout()); |
| run_loop.Run(); |
| |
| // NOTE: A lack of calls could technically also be caused by the HangWatcher |
| // thread executing too slowly / being descheduled. This is a known |
| // limitation. It's expected for |TestTimeouts::tiny_timeout()| to be large |
| // enough that this happens rarely. |
| } |
| |
| namespace { |
| class WatchHangsInScopeBlockingTest : public testing::Test { |
| public: |
| WatchHangsInScopeBlockingTest() { |
| feature_list_.InitWithFeaturesAndParameters(kFeatureAndParams, {}); |
| hang_watcher_.InitializeOnMainThread( |
| HangWatcher::ProcessType::kBrowserProcess); |
| |
| hang_watcher_.SetOnHangClosureForTesting(base::BindLambdaForTesting([&] { |
| capture_started_.Signal(); |
| // Simulate capturing that takes a long time. |
| PlatformThread::Sleep(base::Milliseconds(500)); |
| |
| continue_capture_.Wait(); |
| completed_capture_ = true; |
| })); |
| |
| hang_watcher_.SetAfterMonitorClosureForTesting( |
| base::BindLambdaForTesting([&] { |
| // Simulate monitoring that takes a long time. |
| PlatformThread::Sleep(base::Milliseconds(500)); |
| completed_monitoring_.Signal(); |
| })); |
| |
| // Make sure no periodic monitoring takes place. |
| hang_watcher_.SetMonitoringPeriodForTesting(kVeryLongDelta); |
| |
| hang_watcher_.Start(); |
| |
| // Register the test main thread for hang watching. |
| unregister_thread_closure_ = |
| HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread); |
| } |
| |
| void TearDown() override { hang_watcher_.UnitializeOnMainThreadForTesting(); } |
| |
| WatchHangsInScopeBlockingTest(const WatchHangsInScopeBlockingTest& other) = |
| delete; |
| WatchHangsInScopeBlockingTest& operator=( |
| const WatchHangsInScopeBlockingTest& other) = delete; |
| |
| void VerifyScopesDontBlock() { |
| // Start a WatchHangsInScope that cannot possibly cause a hang to be |
| // detected. |
| { |
| WatchHangsInScope long_scope(kVeryLongDelta); |
| |
| // Manually trigger a monitoring. |
| hang_watcher_.SignalMonitorEventForTesting(); |
| |
| // Execution has to continue freely here as no capture is in progress. |
| } |
| |
| // Monitoring should not be over yet because the test code should execute |
| // faster when not blocked. |
| EXPECT_FALSE(completed_monitoring_.IsSignaled()); |
| |
| // Wait for the full monitoring process to be complete. This is to prove |
| // that monitoring truly executed and that we raced the signaling. |
| completed_monitoring_.Wait(); |
| |
| // No hang means no capture. |
| EXPECT_FALSE(completed_capture_); |
| } |
| |
| protected: |
| base::WaitableEvent capture_started_; |
| base::WaitableEvent completed_monitoring_; |
| |
| // The HangWatcher waits on this event via the "on hang" closure when a hang |
| // is detected. |
| base::WaitableEvent continue_capture_; |
| bool completed_capture_{false}; |
| |
| base::test::ScopedFeatureList feature_list_; |
| HangWatcher hang_watcher_; |
| base::ScopedClosureRunner unregister_thread_closure_; |
| }; |
| } // namespace |
| |
| // Tests that execution is unimpeded by ~WatchHangsInScope() when no capture |
| // ever takes place. |
| TEST_F(WatchHangsInScopeBlockingTest, ScopeDoesNotBlocksWithoutCapture) { |
| // No capture should take place so |continue_capture_| is not signaled to |
| // create a test hang if one ever does. |
| VerifyScopesDontBlock(); |
| } |
| |
| // Test that execution blocks in ~WatchHangsInScope() for a thread under |
| // watch during the capturing of a hang. |
| TEST_F(WatchHangsInScopeBlockingTest, ScopeBlocksDuringCapture) { |
| // The capture completing is not dependent on any test event. Signal to make |
| // sure the test is not blocked. |
| continue_capture_.Signal(); |
| |
| // Start a WatchHangsInScope that expires in the past already. Ensures |
| // that the first monitor will detect a hang. |
| { |
| // Start a WatchHangsInScope that expires right away. Ensures that the |
| // first monitor will detect a hang. |
| WatchHangsInScope expires_right_away(base::TimeDelta{}); |
| |
| // Manually trigger a monitoring. |
| hang_watcher_.SignalMonitorEventForTesting(); |
| |
| // Ensure that the hang capturing started. |
| capture_started_.Wait(); |
| |
| // Execution will get stuck in the outer scope because it can't escape |
| // ~WatchHangsInScope() if a hang capture is under way. |
| } |
| |
| // A hang was in progress so execution should have been blocked in |
| // BlockWhileCaptureInProgress() until capture finishes. |
| EXPECT_TRUE(completed_capture_); |
| completed_monitoring_.Wait(); |
| |
| // Reset expectations |
| completed_monitoring_.Reset(); |
| capture_started_.Reset(); |
| completed_capture_ = false; |
| |
| // Verify that scopes don't block just because a capture happened in the past. |
| VerifyScopesDontBlock(); |
| } |
| |
| #if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_ARM64) |
| // Flaky hangs on arm64 Macs: https://crbug.com/1140207 |
| #define MAYBE_NewScopeDoesNotBlockDuringCapture \ |
| DISABLED_NewScopeDoesNotBlockDuringCapture |
| #else |
| #define MAYBE_NewScopeDoesNotBlockDuringCapture \ |
| NewScopeDoesNotBlockDuringCapture |
| #endif |
| |
| // Test that execution does not block in ~WatchHangsInScope() when the scope |
| // was created after the start of a capture. |
| TEST_F(WatchHangsInScopeBlockingTest, MAYBE_NewScopeDoesNotBlockDuringCapture) { |
| // Start a WatchHangsInScope that expires right away. Ensures that the |
| // first monitor will detect a hang. |
| WatchHangsInScope expires_right_away(base::TimeDelta{}); |
| |
| // Manually trigger a monitoring. |
| hang_watcher_.SignalMonitorEventForTesting(); |
| |
| // Ensure that the hang capturing started. |
| capture_started_.Wait(); |
| |
| // A scope started once a capture is already under way should not block |
| // execution. |
| { WatchHangsInScope also_expires_right_away(base::TimeDelta{}); } |
| |
| // Wait for the new WatchHangsInScope to be destroyed to let the capture |
| // finish. If the new scope block waiting for the capture to finish this would |
| // create a deadlock and the test would hang. |
| continue_capture_.Signal(); |
| } |
| |
| namespace internal { |
| namespace { |
| |
| constexpr std::array<HangWatchDeadline::Flag, 3> kAllFlags{ |
| {HangWatchDeadline::Flag::kMinValue, |
| HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope, |
| HangWatchDeadline::Flag::kShouldBlockOnHang}}; |
| } // namespace |
| |
| class HangWatchDeadlineTest : public testing::Test { |
| protected: |
| void AssertNoFlagsSet() const { |
| for (HangWatchDeadline::Flag flag : kAllFlags) { |
| ASSERT_FALSE(deadline_.IsFlagSet(flag)); |
| } |
| } |
| |
| // Return a flag mask without one of the flags for test purposes. Use to |
| // ignore that effect of setting a flag that was just set. |
| uint64_t FlagsMinus(uint64_t flags, HangWatchDeadline::Flag flag) { |
| return flags & ~(static_cast<uint64_t>(flag)); |
| } |
| |
| HangWatchDeadline deadline_; |
| }; |
| |
| // Verify that the extract functions don't mangle any bits. |
| TEST_F(HangWatchDeadlineTest, BitsPreservedThroughExtract) { |
| for (auto bits : {kAllOnes, kAllZeros, kOnesThenZeroes, kZeroesThenOnes}) { |
| ASSERT_TRUE((HangWatchDeadline::ExtractFlags(bits) | |
| HangWatchDeadline::ExtractDeadline(bits)) == bits); |
| } |
| } |
| |
| // Verify that setting and clearing a persistent flag works and has no unwanted |
| // side-effects. Neither the flags nor the deadline change concurrently in this |
| // test. |
| TEST_F(HangWatchDeadlineTest, SetAndClearPersistentFlag) { |
| AssertNoFlagsSet(); |
| |
| // Grab the original values for flags and deadline. |
| auto [old_flags, old_deadline] = deadline_.GetFlagsAndDeadline(); |
| |
| // Set the flag. Operation cannot fail. |
| deadline_.SetIgnoreCurrentWatchHangsInScope(); |
| |
| // Get new flags and deadline. |
| auto [new_flags, new_deadline] = deadline_.GetFlagsAndDeadline(); |
| |
| // Flag was set properly. |
| ASSERT_TRUE(HangWatchDeadline::IsFlagSet( |
| HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope, new_flags)); |
| |
| // No side-effect on deadline. |
| ASSERT_EQ(new_deadline, old_deadline); |
| |
| // No side-effect on other flags. |
| ASSERT_EQ( |
| FlagsMinus(new_flags, |
| HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope), |
| old_flags); |
| |
| // Clear the flag, operation cannot fail. |
| deadline_.UnsetIgnoreCurrentWatchHangsInScope(); |
| |
| // Update new values. |
| std::tie(new_flags, new_deadline) = deadline_.GetFlagsAndDeadline(); |
| |
| // All flags back to original state. |
| ASSERT_EQ(new_flags, old_flags); |
| |
| // Deadline still unnafected. |
| ASSERT_EQ(new_deadline, old_deadline); |
| } |
| |
| // Verify setting the TimeTicks value works and has no unwanted side-effects. |
| TEST_F(HangWatchDeadlineTest, SetDeadline) { |
| TimeTicks ticks; |
| |
| AssertNoFlagsSet(); |
| ASSERT_NE(deadline_.GetDeadline(), ticks); |
| |
| // Set the deadline and verify it stuck. |
| deadline_.SetDeadline(ticks); |
| ASSERT_EQ(deadline_.GetDeadline(), ticks); |
| |
| // Only the value was modified, no flags should be set. |
| AssertNoFlagsSet(); |
| } |
| |
| // Verify that setting a non-persistent flag (kShouldBlockOnHang) |
| // when the TimeTicks value changed since calling the flag setting |
| // function fails and has no side-effects. |
| TEST_F(HangWatchDeadlineTest, SetShouldBlockOnHangDeadlineChanged) { |
| AssertNoFlagsSet(); |
| |
| auto [flags, deadline] = deadline_.GetFlagsAndDeadline(); |
| |
| // Simulate value change. Flags are constant. |
| const base::TimeTicks new_deadline = |
| base::TimeTicks::FromInternalValue(kArbitraryDeadline); |
| ASSERT_NE(deadline, new_deadline); |
| deadline_.SetSwitchBitsClosureForTesting( |
| base::BindLambdaForTesting([]() { return kArbitraryDeadline; })); |
| |
| // kShouldBlockOnHangs does not persist through value change. |
| ASSERT_FALSE(deadline_.SetShouldBlockOnHang(flags, deadline)); |
| |
| // Flag was not applied. |
| ASSERT_FALSE( |
| deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang)); |
| |
| // New value that was changed concurrently is preserved. |
| ASSERT_EQ(deadline_.GetDeadline(), new_deadline); |
| } |
| |
| // Verify that clearing a persistent (kIgnoreCurrentWatchHangsInScope) when |
| // the value changed succeeds and has non side-effects. |
| TEST_F(HangWatchDeadlineTest, ClearIgnoreHangsDeadlineChanged) { |
| AssertNoFlagsSet(); |
| |
| auto [flags, deadline] = deadline_.GetFlagsAndDeadline(); |
| |
| deadline_.SetIgnoreCurrentWatchHangsInScope(); |
| std::tie(flags, deadline) = deadline_.GetFlagsAndDeadline(); |
| ASSERT_TRUE(HangWatchDeadline::IsFlagSet( |
| HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope, flags)); |
| |
| // Simulate deadline change. Flags are constant. |
| const base::TimeTicks new_deadline = |
| base::TimeTicks::FromInternalValue(kArbitraryDeadline); |
| ASSERT_NE(deadline, new_deadline); |
| deadline_.SetSwitchBitsClosureForTesting(base::BindLambdaForTesting([]() { |
| return static_cast<uint64_t>(HangWatchDeadline::Flag::kShouldBlockOnHang) | |
| kArbitraryDeadline; |
| })); |
| |
| // Clearing kIgnoreHang is unaffected by deadline or flags change. |
| deadline_.UnsetIgnoreCurrentWatchHangsInScope(); |
| ASSERT_FALSE(deadline_.IsFlagSet( |
| HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope)); |
| |
| // New deadline that was changed concurrently is preserved. |
| ASSERT_TRUE(deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang)); |
| ASSERT_EQ(deadline_.GetDeadline(), new_deadline); |
| } |
| |
| // Verify that setting a persistent (kIgnoreCurrentWatchHangsInScope) when |
| // the deadline or flags changed succeeds and has non side-effects. |
| TEST_F(HangWatchDeadlineTest, |
| SetIgnoreCurrentHangWatchScopeEnableDeadlineChangedd) { |
| AssertNoFlagsSet(); |
| |
| auto [flags, deadline] = deadline_.GetFlagsAndDeadline(); |
| |
| // Simulate deadline change. Flags are constant. |
| const base::TimeTicks new_deadline = |
| base::TimeTicks::FromInternalValue(kArbitraryDeadline); |
| |
| ASSERT_NE(deadline, new_deadline); |
| deadline_.SetSwitchBitsClosureForTesting(base::BindLambdaForTesting([]() { |
| return static_cast<uint64_t>(HangWatchDeadline::Flag::kShouldBlockOnHang) | |
| kArbitraryDeadline; |
| })); |
| |
| // kIgnoreHang persists through value change. |
| deadline_.SetIgnoreCurrentWatchHangsInScope(); |
| ASSERT_TRUE(deadline_.IsFlagSet( |
| HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope)); |
| |
| // New deadline and flags that changed concurrently are preserved. |
| ASSERT_TRUE(deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang)); |
| ASSERT_EQ(deadline_.GetDeadline(), new_deadline); |
| } |
| |
| // Setting a new deadline should wipe flags that a not persistent. |
| // Persistent flags should not be disturbed. |
| TEST_F(HangWatchDeadlineTest, SetDeadlineWipesFlags) { |
| auto [flags, deadline] = deadline_.GetFlagsAndDeadline(); |
| |
| ASSERT_TRUE(deadline_.SetShouldBlockOnHang(flags, deadline)); |
| ASSERT_TRUE(deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang)); |
| |
| std::tie(flags, deadline) = deadline_.GetFlagsAndDeadline(); |
| |
| deadline_.SetIgnoreCurrentWatchHangsInScope(); |
| ASSERT_TRUE(deadline_.IsFlagSet( |
| HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope)); |
| |
| // Change the deadline. |
| deadline_.SetDeadline(TimeTicks{}); |
| ASSERT_EQ(deadline_.GetDeadline(), TimeTicks{}); |
| |
| // Verify the persistent flag stuck and the non-persistent one was unset. |
| ASSERT_FALSE( |
| deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang)); |
| ASSERT_TRUE(deadline_.IsFlagSet( |
| HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope)); |
| } |
| |
| } // namespace internal |
| |
| } // namespace base |