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