// Copyright 2024 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/synchronization/cancelable_event.h" #include #include #include #include #include #include "base/barrier_closure.h" #include "base/functional/callback.h" #include "base/logging.h" #include "base/numerics/clamped_math.h" #include "base/rand_util.h" #include "base/strings/stringprintf.h" #include "base/synchronization/lock.h" #include "base/test/bind.h" #include "base/test/test_timeouts.h" #include "base/test/test_waitable_event.h" #include "base/threading/thread.h" #include "base/time/time.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/platform_test.h" namespace base { namespace { class CancelableEventTest : public testing::Test { protected: raw_ptr CreateThreadWithTask(RepeatingClosure& thread_task) { std::unique_ptr thread = std::make_unique( StringPrintf("CancelTestThread%d", threadcounter++)); thread->Start(); thread->task_runner()->PostTask(FROM_HERE, thread_task); threads_.push_back(std::move(thread)); return threads_.back().get(); } int threadcounter = 0; WaitableEvent shutdown_event_; std::vector> threads_; }; } // namespace TEST_F(CancelableEventTest, TimedWaitFail) { CancelableEvent event; RepeatingClosure task = BindLambdaForTesting([&]() { TimeTicks start_time = TimeTicks::Now(); EXPECT_FALSE(event.TimedWait(TestTimeouts::tiny_timeout())); EXPECT_GE(TimeTicks::Now() - start_time, TestTimeouts::tiny_timeout()); }); this->CreateThreadWithTask(task)->FlushForTesting(); } TEST_F(CancelableEventTest, TimedWaitSuccess) { CancelableEvent event; RepeatingClosure task = BindLambdaForTesting( [&]() { EXPECT_TRUE(event.TimedWait(TestTimeouts::tiny_timeout())); }); event.Signal(); this->CreateThreadWithTask(task)->FlushForTesting(); } // These are the platforms on which a functional CancelableEvent is implemented. #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || \ BUILDFLAG(IS_ANDROID) TEST_F(CancelableEventTest, CancelSucceedsWhenNoWaiterAndWaitTimesOut) { CancelableEvent event; event.Signal(); EXPECT_TRUE(event.Cancel()); EXPECT_FALSE(event.TimedWait(base::TimeDelta())); } TEST_F(CancelableEventTest, BothCancelFailureAndSucceedOccurWithOneWaiter) { bool cancel_failed = false; bool cancel_succeeded = false; for (int i = 0; i < 100; ++i) { CancelableEvent event; TestWaitableEvent thread_running; auto task = BindLambdaForTesting([&]() { thread_running.Signal(); event.Wait(); }); auto thread = CreateThreadWithTask(task); if (!cancel_failed) { thread_running.Wait(); } event.Signal(); #if BUILDFLAG(IS_POSIX) // Posix implementations of Semaphores seem to be much less greedy in waking // up threads currently waiting on the event - give the thread a few // milliseconds to wake up and acquire the semaphore before us. if (cancel_succeeded) { PlatformThread::Sleep(Milliseconds(8)); } #endif if (event.Cancel()) { cancel_succeeded = true; event.Signal(); } else { cancel_failed = true; } thread->FlushForTesting(); if (cancel_failed && cancel_succeeded) { break; } } EXPECT_TRUE(cancel_failed); EXPECT_TRUE(cancel_succeeded); } TEST_F(CancelableEventTest, BothCancelFailureAndSucceedOccurUnderContention) { // The following block is responsible for creating CPU contention - it creates // 16 threads which run for the duration of the test, crunching CPU. None of // the data here is used in any meaningful way in the rest of the test. beyond // setting `busywork_threads_should_quit` to signal exit. std::atomic_bool busywork_threads_should_quit = false; // The arena lives for the duration of the test, and so must have test-wide // scope, however it is only accessed by the busywork threads, and not by the // rest of the test. const int kNumThreads = 16; std::atomic_char busywork_arena[kNumThreads]; { TestWaitableEvent threads_running; RepeatingClosure threads_running_barrier = BarrierClosure( kNumThreads, BindOnce(&TestWaitableEvent::Signal, Unretained(&threads_running))); for (int i = 0; i < kNumThreads; ++i) { auto task = BindLambdaForTesting([&]() { threads_running_barrier.Run(); while (!busywork_threads_should_quit.load(std::memory_order_acquire)) { // Busy loop. Only done to burn CPU. uint64_t counter = 1; for (auto& slot : busywork_arena) { counter += slot + 1; slot.store(static_cast((counter & 0xf) + 1), std::memory_order_release); } } }); CreateThreadWithTask(task); } threads_running.Wait(); } // Used to adjust race timings to favor the case which has not yet been seen // (cancel fail/success). internal::ClampedNumeric wait_ms = 0; bool succeeded = false; bool failed = false; for (int i = 0; i < 10 && (!failed || !succeeded); ++i) { CancelableEvent event; TestWaitableEvent thread_running; std::atomic_bool thread_done = false; auto task = BindLambdaForTesting([&]() { thread_running.Signal(); EXPECT_TRUE(event.TimedWait(TestTimeouts::test_launcher_timeout())); thread_done = true; }); auto thread = CreateThreadWithTask(task); PlatformThread::Sleep(Milliseconds(wait_ms)); event.Signal(); PlatformThread::Sleep(Milliseconds(wait_ms)); if (event.Cancel()) { succeeded = true; event.Signal(); wait_ms += 100; } else { failed = true; wait_ms -= 50; } thread->FlushForTesting(); EXPECT_TRUE(thread_done); } busywork_threads_should_quit.store(true, std::memory_order_release); for (auto& thread : threads_) { thread->FlushForTesting(); } EXPECT_TRUE(succeeded); EXPECT_TRUE(failed); } #else TEST_F(CancelableEventTest, CancelFailsOnUnsupportedPlatforms) { CancelableEvent event; event.Signal(); EXPECT_FALSE(event.Cancel()); EXPECT_TRUE(event.TimedWait(base::TimeDelta())); } #endif } // namespace base