1 // Copyright 2018 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/task/common/operations_controller.h"
6
7 #include <atomic>
8 #include <cstdint>
9 #include <utility>
10
11 #include "base/memory/raw_ref.h"
12 #include "base/ranges/algorithm.h"
13 #include "base/threading/platform_thread.h"
14 #include "base/threading/simple_thread.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16
17 namespace base {
18 namespace internal {
19 namespace {
20
21 class ScopedShutdown {
22 public:
ScopedShutdown(OperationsController * controller)23 ScopedShutdown(OperationsController* controller) : controller_(*controller) {}
~ScopedShutdown()24 ~ScopedShutdown() { controller_->ShutdownAndWaitForZeroOperations(); }
25
26 private:
27 const raw_ref<OperationsController> controller_;
28 };
29
TEST(OperationsControllerTest,CanBeDestroyedWithoutWaiting)30 TEST(OperationsControllerTest, CanBeDestroyedWithoutWaiting) {
31 OperationsController controller;
32 }
33
TEST(OperationsControllerTest,CanShutdownIfNotStarted)34 TEST(OperationsControllerTest, CanShutdownIfNotStarted) {
35 OperationsController controller;
36
37 controller.ShutdownAndWaitForZeroOperations();
38 }
39
TEST(OperationsControllerTest,FailsToBeginWhenNotStarted)40 TEST(OperationsControllerTest, FailsToBeginWhenNotStarted) {
41 OperationsController controller;
42
43 auto operation_token = controller.TryBeginOperation();
44
45 EXPECT_FALSE(operation_token);
46 }
47
TEST(OperationsControllerTest,CanShutdownAfterTryCallsIfNotStarted)48 TEST(OperationsControllerTest, CanShutdownAfterTryCallsIfNotStarted) {
49 OperationsController controller;
50 auto operation_token = controller.TryBeginOperation();
51 ASSERT_FALSE(operation_token);
52
53 controller.ShutdownAndWaitForZeroOperations();
54 }
55
TEST(OperationsControllerTest,StartAcceptingOperationsReturnsFalseIfNoRejectedBeginAttempts)56 TEST(OperationsControllerTest,
57 StartAcceptingOperationsReturnsFalseIfNoRejectedBeginAttempts) {
58 OperationsController controller;
59 ScopedShutdown cleanup(&controller);
60
61 EXPECT_FALSE(controller.StartAcceptingOperations());
62 }
63
TEST(OperationsControllerTest,StartAcceptingOperationsReturnsTrueIfFailedBeginAttempts)64 TEST(OperationsControllerTest,
65 StartAcceptingOperationsReturnsTrueIfFailedBeginAttempts) {
66 OperationsController controller;
67 ScopedShutdown cleanup(&controller);
68
69 auto operation_token = controller.TryBeginOperation();
70 ASSERT_FALSE(operation_token);
71
72 EXPECT_TRUE(controller.StartAcceptingOperations());
73 }
74
TEST(OperationsControllerTest,SuccesfulBeginReturnsValidScopedObject)75 TEST(OperationsControllerTest, SuccesfulBeginReturnsValidScopedObject) {
76 OperationsController controller;
77 ScopedShutdown cleanup(&controller);
78 controller.StartAcceptingOperations();
79
80 auto operation_token = controller.TryBeginOperation();
81
82 EXPECT_TRUE(operation_token);
83 }
84
TEST(OperationsControllerTest,BeginFailsAfterShutdown)85 TEST(OperationsControllerTest, BeginFailsAfterShutdown) {
86 OperationsController controller;
87 controller.StartAcceptingOperations();
88
89 controller.ShutdownAndWaitForZeroOperations();
90 auto operation_token = controller.TryBeginOperation();
91
92 EXPECT_FALSE(operation_token);
93 }
94
TEST(OperationsControllerTest,ScopedOperationsControllerIsMoveConstructible)95 TEST(OperationsControllerTest, ScopedOperationsControllerIsMoveConstructible) {
96 OperationsController controller;
97 ScopedShutdown cleanup(&controller);
98
99 controller.StartAcceptingOperations();
100 auto operation_token_1 = controller.TryBeginOperation();
101 auto operation_token_2 = std::move(operation_token_1);
102
103 EXPECT_FALSE(operation_token_1);
104 EXPECT_TRUE(operation_token_2);
105 }
106
107 // Dummy SimpleThread implementation that periodically begins and ends
108 // operations until one of them fails.
109 class TestThread : public SimpleThread {
110 public:
TestThread(OperationsController * ref_controller,std::atomic<bool> * started,std::atomic<int32_t> * thread_counter)111 explicit TestThread(OperationsController* ref_controller,
112 std::atomic<bool>* started,
113 std::atomic<int32_t>* thread_counter)
114 : SimpleThread("TestThread"),
115 controller_(*ref_controller),
116 started_(*started),
117 thread_counter_(*thread_counter) {}
Run()118 void Run() override {
119 thread_counter_->fetch_add(1, std::memory_order_relaxed);
120 while (true) {
121 PlatformThread::YieldCurrentThread();
122 bool was_started = started_->load(std::memory_order_relaxed);
123 std::vector<OperationsController::OperationToken> tokens;
124 for (int i = 0; i < 100; ++i) {
125 tokens.push_back(controller_->TryBeginOperation());
126 }
127 if (!was_started)
128 continue;
129 if (ranges::any_of(tokens, [](const auto& token) { return !token; })) {
130 break;
131 }
132 }
133 }
134
135 private:
136 const raw_ref<OperationsController> controller_;
137 const raw_ref<std::atomic<bool>> started_;
138 const raw_ref<std::atomic<int32_t>> thread_counter_;
139 };
140
TEST(OperationsControllerTest,BeginsFromMultipleThreads)141 TEST(OperationsControllerTest, BeginsFromMultipleThreads) {
142 constexpr int32_t kNumThreads = 10;
143 for (int32_t i = 0; i < 10; ++i) {
144 OperationsController ref_controller;
145 std::atomic<bool> started(false);
146 std::atomic<int32_t> running_threads(0);
147 std::vector<std::unique_ptr<TestThread>> threads;
148 for (int j = 0; j < kNumThreads; ++j) {
149 threads.push_back(std::make_unique<TestThread>(&ref_controller, &started,
150 &running_threads));
151 threads.back()->Start();
152 }
153
154 // Make sure all threads are running.
155 while (running_threads.load(std::memory_order_relaxed) != kNumThreads) {
156 PlatformThread::YieldCurrentThread();
157 }
158
159 // Wait a bit before starting to try to introduce races.
160 constexpr TimeDelta kRaceInducingTimeout = Microseconds(50);
161 PlatformThread::Sleep(kRaceInducingTimeout);
162
163 ref_controller.StartAcceptingOperations();
164 // Signal threads to terminate on TryBeginOperation() failures
165 started.store(true, std::memory_order_relaxed);
166
167 // Let the test run for a while before shuting down.
168 PlatformThread::Sleep(Milliseconds(5));
169 ref_controller.ShutdownAndWaitForZeroOperations();
170 for (const auto& t : threads) {
171 t->Join();
172 }
173 }
174 }
175
176 } // namespace
177 } // namespace internal
178 } // namespace base
179