1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203

base / synchronization / cancelable_event_unittest.cc [blame]

// 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