1 // Copyright 2012 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/threading/thread_collision_warner.h"
6
7 #include <memory>
8
9 #include "base/compiler_specific.h"
10 #include "base/memory/raw_ptr.h"
11 #include "base/synchronization/lock.h"
12 #include "base/threading/platform_thread.h"
13 #include "base/threading/simple_thread.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15
16 #if defined(NDEBUG)
17
18 // Would cause a memory leak otherwise.
19 #undef DFAKE_MUTEX
20 #define DFAKE_MUTEX(obj) std::unique_ptr<base::AsserterBase> obj
21
22 // In Release, we expect the AsserterBase::warn() to not happen.
23 #define EXPECT_NDEBUG_FALSE_DEBUG_TRUE EXPECT_FALSE
24
25 #else
26
27 // In Debug, we expect the AsserterBase::warn() to happen.
28 #define EXPECT_NDEBUG_FALSE_DEBUG_TRUE EXPECT_TRUE
29
30 #endif
31
32
33 namespace {
34
35 // This is the asserter used with ThreadCollisionWarner instead of the default
36 // DCheckAsserter. The method fail_state is used to know if a collision took
37 // place.
38 class AssertReporter : public base::AsserterBase {
39 public:
AssertReporter()40 AssertReporter()
41 : failed_(false) {}
42
warn()43 void warn() override { failed_ = true; }
44
45 ~AssertReporter() override = default;
46
fail_state() const47 bool fail_state() const { return failed_; }
reset()48 void reset() { failed_ = false; }
49
50 private:
51 bool failed_;
52 };
53
54 } // namespace
55
TEST(ThreadCollisionTest,BookCriticalSection)56 TEST(ThreadCollisionTest, BookCriticalSection) {
57 AssertReporter* local_reporter = new AssertReporter();
58
59 base::ThreadCollisionWarner warner(local_reporter);
60 EXPECT_FALSE(local_reporter->fail_state());
61
62 { // Pin section.
63 DFAKE_SCOPED_LOCK_THREAD_LOCKED(warner);
64 EXPECT_FALSE(local_reporter->fail_state());
65 { // Pin section.
66 DFAKE_SCOPED_LOCK_THREAD_LOCKED(warner);
67 EXPECT_FALSE(local_reporter->fail_state());
68 }
69 }
70 }
71
TEST(ThreadCollisionTest,ScopedRecursiveBookCriticalSection)72 TEST(ThreadCollisionTest, ScopedRecursiveBookCriticalSection) {
73 AssertReporter* local_reporter = new AssertReporter();
74
75 base::ThreadCollisionWarner warner(local_reporter);
76 EXPECT_FALSE(local_reporter->fail_state());
77
78 { // Pin section.
79 DFAKE_SCOPED_RECURSIVE_LOCK(warner);
80 EXPECT_FALSE(local_reporter->fail_state());
81 { // Pin section again (allowed by DFAKE_SCOPED_RECURSIVE_LOCK)
82 DFAKE_SCOPED_RECURSIVE_LOCK(warner);
83 EXPECT_FALSE(local_reporter->fail_state());
84 } // Unpin section.
85 } // Unpin section.
86
87 // Check that section is not pinned
88 { // Pin section.
89 DFAKE_SCOPED_LOCK(warner);
90 EXPECT_FALSE(local_reporter->fail_state());
91 } // Unpin section.
92 }
93
TEST(ThreadCollisionTest,ScopedBookCriticalSection)94 TEST(ThreadCollisionTest, ScopedBookCriticalSection) {
95 AssertReporter* local_reporter = new AssertReporter();
96
97 base::ThreadCollisionWarner warner(local_reporter);
98 EXPECT_FALSE(local_reporter->fail_state());
99
100 { // Pin section.
101 DFAKE_SCOPED_LOCK(warner);
102 EXPECT_FALSE(local_reporter->fail_state());
103 } // Unpin section.
104
105 { // Pin section.
106 DFAKE_SCOPED_LOCK(warner);
107 EXPECT_FALSE(local_reporter->fail_state());
108 {
109 // Pin section again (not allowed by DFAKE_SCOPED_LOCK)
110 DFAKE_SCOPED_LOCK(warner);
111 EXPECT_NDEBUG_FALSE_DEBUG_TRUE(local_reporter->fail_state());
112 // Reset the status of warner for further tests.
113 local_reporter->reset();
114 } // Unpin section.
115 } // Unpin section.
116
117 {
118 // Pin section.
119 DFAKE_SCOPED_LOCK(warner);
120 EXPECT_FALSE(local_reporter->fail_state());
121 } // Unpin section.
122 }
123
TEST(ThreadCollisionTest,MTBookCriticalSectionTest)124 TEST(ThreadCollisionTest, MTBookCriticalSectionTest) {
125 class NonThreadSafeQueue {
126 public:
127 explicit NonThreadSafeQueue(base::AsserterBase* asserter)
128 : push_pop_(asserter) {
129 }
130
131 NonThreadSafeQueue(const NonThreadSafeQueue&) = delete;
132 NonThreadSafeQueue& operator=(const NonThreadSafeQueue&) = delete;
133
134 void push(int value) {
135 DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_);
136 }
137
138 int pop() {
139 DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_);
140 return 0;
141 }
142
143 private:
144 DFAKE_MUTEX(push_pop_);
145 };
146
147 class QueueUser : public base::DelegateSimpleThread::Delegate {
148 public:
149 explicit QueueUser(NonThreadSafeQueue* queue) : queue_(queue) {}
150
151 void Run() override {
152 queue_->push(0);
153 queue_->pop();
154 }
155
156 private:
157 raw_ptr<NonThreadSafeQueue> queue_;
158 };
159
160 AssertReporter* local_reporter = new AssertReporter();
161
162 NonThreadSafeQueue queue(local_reporter);
163
164 QueueUser queue_user_a(&queue);
165 QueueUser queue_user_b(&queue);
166
167 base::DelegateSimpleThread thread_a(&queue_user_a, "queue_user_thread_a");
168 base::DelegateSimpleThread thread_b(&queue_user_b, "queue_user_thread_b");
169
170 thread_a.Start();
171 thread_b.Start();
172
173 thread_a.Join();
174 thread_b.Join();
175
176 EXPECT_NDEBUG_FALSE_DEBUG_TRUE(local_reporter->fail_state());
177 }
178
TEST(ThreadCollisionTest,MTScopedBookCriticalSectionTest)179 TEST(ThreadCollisionTest, MTScopedBookCriticalSectionTest) {
180 // Queue with a 5 seconds push execution time, hopefuly the two used threads
181 // in the test will enter the push at same time.
182 class NonThreadSafeQueue {
183 public:
184 explicit NonThreadSafeQueue(base::AsserterBase* asserter)
185 : push_pop_(asserter) {
186 }
187
188 NonThreadSafeQueue(const NonThreadSafeQueue&) = delete;
189 NonThreadSafeQueue& operator=(const NonThreadSafeQueue&) = delete;
190
191 void push(int value) {
192 DFAKE_SCOPED_LOCK(push_pop_);
193 base::PlatformThread::Sleep(base::Seconds(5));
194 }
195
196 int pop() {
197 DFAKE_SCOPED_LOCK(push_pop_);
198 return 0;
199 }
200
201 private:
202 DFAKE_MUTEX(push_pop_);
203 };
204
205 class QueueUser : public base::DelegateSimpleThread::Delegate {
206 public:
207 explicit QueueUser(NonThreadSafeQueue* queue) : queue_(queue) {}
208
209 void Run() override {
210 queue_->push(0);
211 queue_->pop();
212 }
213
214 private:
215 raw_ptr<NonThreadSafeQueue> queue_;
216 };
217
218 AssertReporter* local_reporter = new AssertReporter();
219
220 NonThreadSafeQueue queue(local_reporter);
221
222 QueueUser queue_user_a(&queue);
223 QueueUser queue_user_b(&queue);
224
225 base::DelegateSimpleThread thread_a(&queue_user_a, "queue_user_thread_a");
226 base::DelegateSimpleThread thread_b(&queue_user_b, "queue_user_thread_b");
227
228 thread_a.Start();
229 thread_b.Start();
230
231 thread_a.Join();
232 thread_b.Join();
233
234 EXPECT_NDEBUG_FALSE_DEBUG_TRUE(local_reporter->fail_state());
235 }
236
TEST(ThreadCollisionTest,MTSynchedScopedBookCriticalSectionTest)237 TEST(ThreadCollisionTest, MTSynchedScopedBookCriticalSectionTest) {
238 // Queue with a 2 seconds push execution time, hopefuly the two used threads
239 // in the test will enter the push at same time.
240 class NonThreadSafeQueue {
241 public:
242 explicit NonThreadSafeQueue(base::AsserterBase* asserter)
243 : push_pop_(asserter) {
244 }
245
246 NonThreadSafeQueue(const NonThreadSafeQueue&) = delete;
247 NonThreadSafeQueue& operator=(const NonThreadSafeQueue&) = delete;
248
249 void push(int value) {
250 DFAKE_SCOPED_LOCK(push_pop_);
251 base::PlatformThread::Sleep(base::Seconds(2));
252 }
253
254 int pop() {
255 DFAKE_SCOPED_LOCK(push_pop_);
256 return 0;
257 }
258
259 private:
260 DFAKE_MUTEX(push_pop_);
261 };
262
263 // This time the QueueUser class protects the non thread safe queue with
264 // a lock.
265 class QueueUser : public base::DelegateSimpleThread::Delegate {
266 public:
267 QueueUser(NonThreadSafeQueue* queue, base::Lock* lock)
268 : queue_(queue), lock_(lock) {}
269
270 void Run() override {
271 {
272 base::AutoLock auto_lock(*lock_);
273 queue_->push(0);
274 }
275 {
276 base::AutoLock auto_lock(*lock_);
277 queue_->pop();
278 }
279 }
280 private:
281 raw_ptr<NonThreadSafeQueue> queue_;
282 raw_ptr<base::Lock> lock_;
283 };
284
285 AssertReporter* local_reporter = new AssertReporter();
286
287 NonThreadSafeQueue queue(local_reporter);
288
289 base::Lock lock;
290
291 QueueUser queue_user_a(&queue, &lock);
292 QueueUser queue_user_b(&queue, &lock);
293
294 base::DelegateSimpleThread thread_a(&queue_user_a, "queue_user_thread_a");
295 base::DelegateSimpleThread thread_b(&queue_user_b, "queue_user_thread_b");
296
297 thread_a.Start();
298 thread_b.Start();
299
300 thread_a.Join();
301 thread_b.Join();
302
303 EXPECT_FALSE(local_reporter->fail_state());
304 }
305
TEST(ThreadCollisionTest,MTSynchedScopedRecursiveBookCriticalSectionTest)306 TEST(ThreadCollisionTest, MTSynchedScopedRecursiveBookCriticalSectionTest) {
307 // Queue with a 2 seconds push execution time, hopefuly the two used threads
308 // in the test will enter the push at same time.
309 class NonThreadSafeQueue {
310 public:
311 explicit NonThreadSafeQueue(base::AsserterBase* asserter)
312 : push_pop_(asserter) {
313 }
314
315 NonThreadSafeQueue(const NonThreadSafeQueue&) = delete;
316 NonThreadSafeQueue& operator=(const NonThreadSafeQueue&) = delete;
317
318 void push(int) {
319 DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_);
320 bar();
321 base::PlatformThread::Sleep(base::Seconds(2));
322 }
323
324 int pop() {
325 DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_);
326 return 0;
327 }
328
329 void bar() {
330 DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_);
331 }
332
333 private:
334 DFAKE_MUTEX(push_pop_);
335 };
336
337 // This time the QueueUser class protects the non thread safe queue with
338 // a lock.
339 class QueueUser : public base::DelegateSimpleThread::Delegate {
340 public:
341 QueueUser(NonThreadSafeQueue* queue, base::Lock* lock)
342 : queue_(queue), lock_(lock) {}
343
344 void Run() override {
345 {
346 base::AutoLock auto_lock(*lock_);
347 queue_->push(0);
348 }
349 {
350 base::AutoLock auto_lock(*lock_);
351 queue_->bar();
352 }
353 {
354 base::AutoLock auto_lock(*lock_);
355 queue_->pop();
356 }
357 }
358 private:
359 raw_ptr<NonThreadSafeQueue> queue_;
360 raw_ptr<base::Lock> lock_;
361 };
362
363 AssertReporter* local_reporter = new AssertReporter();
364
365 NonThreadSafeQueue queue(local_reporter);
366
367 base::Lock lock;
368
369 QueueUser queue_user_a(&queue, &lock);
370 QueueUser queue_user_b(&queue, &lock);
371
372 base::DelegateSimpleThread thread_a(&queue_user_a, "queue_user_thread_a");
373 base::DelegateSimpleThread thread_b(&queue_user_b, "queue_user_thread_b");
374
375 thread_a.Start();
376 thread_b.Start();
377
378 thread_a.Join();
379 thread_b.Join();
380
381 EXPECT_FALSE(local_reporter->fail_state());
382 }
383