| // 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 <stdint.h> |
| |
| #include <atomic> |
| #include <memory> |
| #include <tuple> |
| #include <vector> |
| |
| #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<Thread> CreateThreadWithTask(RepeatingClosure& thread_task) { |
| std::unique_ptr<Thread> thread = std::make_unique<Thread>( |
| 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<std::unique_ptr<Thread>> 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<char>((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<unsigned int> 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 |