|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "base/threading/thread_collision_warner.h" | 
|  |  | 
|  | #include <memory> | 
|  |  | 
|  | #include "base/compiler_specific.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/synchronization/lock.h" | 
|  | #include "base/threading/platform_thread.h" | 
|  | #include "base/threading/simple_thread.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | // '' : local class member function does not have a body | 
|  | MSVC_PUSH_DISABLE_WARNING(4822) | 
|  |  | 
|  |  | 
|  | #if defined(NDEBUG) | 
|  |  | 
|  | // Would cause a memory leak otherwise. | 
|  | #undef DFAKE_MUTEX | 
|  | #define DFAKE_MUTEX(obj) std::unique_ptr<base::AsserterBase> obj | 
|  |  | 
|  | // In Release, we expect the AsserterBase::warn() to not happen. | 
|  | #define EXPECT_NDEBUG_FALSE_DEBUG_TRUE EXPECT_FALSE | 
|  |  | 
|  | #else | 
|  |  | 
|  | // In Debug, we expect the AsserterBase::warn() to happen. | 
|  | #define EXPECT_NDEBUG_FALSE_DEBUG_TRUE EXPECT_TRUE | 
|  |  | 
|  | #endif | 
|  |  | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // This is the asserter used with ThreadCollisionWarner instead of the default | 
|  | // DCheckAsserter. The method fail_state is used to know if a collision took | 
|  | // place. | 
|  | class AssertReporter : public base::AsserterBase { | 
|  | public: | 
|  | AssertReporter() | 
|  | : failed_(false) {} | 
|  |  | 
|  | void warn() override { failed_ = true; } | 
|  |  | 
|  | ~AssertReporter() override = default; | 
|  |  | 
|  | bool fail_state() const { return failed_; } | 
|  | void reset() { failed_ = false; } | 
|  |  | 
|  | private: | 
|  | bool failed_; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | TEST(ThreadCollisionTest, BookCriticalSection) { | 
|  | AssertReporter* local_reporter = new AssertReporter(); | 
|  |  | 
|  | base::ThreadCollisionWarner warner(local_reporter); | 
|  | EXPECT_FALSE(local_reporter->fail_state()); | 
|  |  | 
|  | {  // Pin section. | 
|  | DFAKE_SCOPED_LOCK_THREAD_LOCKED(warner); | 
|  | EXPECT_FALSE(local_reporter->fail_state()); | 
|  | {  // Pin section. | 
|  | DFAKE_SCOPED_LOCK_THREAD_LOCKED(warner); | 
|  | EXPECT_FALSE(local_reporter->fail_state()); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(ThreadCollisionTest, ScopedRecursiveBookCriticalSection) { | 
|  | AssertReporter* local_reporter = new AssertReporter(); | 
|  |  | 
|  | base::ThreadCollisionWarner warner(local_reporter); | 
|  | EXPECT_FALSE(local_reporter->fail_state()); | 
|  |  | 
|  | {  // Pin section. | 
|  | DFAKE_SCOPED_RECURSIVE_LOCK(warner); | 
|  | EXPECT_FALSE(local_reporter->fail_state()); | 
|  | {  // Pin section again (allowed by DFAKE_SCOPED_RECURSIVE_LOCK) | 
|  | DFAKE_SCOPED_RECURSIVE_LOCK(warner); | 
|  | EXPECT_FALSE(local_reporter->fail_state()); | 
|  | }  // Unpin section. | 
|  | }  // Unpin section. | 
|  |  | 
|  | // Check that section is not pinned | 
|  | {  // Pin section. | 
|  | DFAKE_SCOPED_LOCK(warner); | 
|  | EXPECT_FALSE(local_reporter->fail_state()); | 
|  | }  // Unpin section. | 
|  | } | 
|  |  | 
|  | TEST(ThreadCollisionTest, ScopedBookCriticalSection) { | 
|  | AssertReporter* local_reporter = new AssertReporter(); | 
|  |  | 
|  | base::ThreadCollisionWarner warner(local_reporter); | 
|  | EXPECT_FALSE(local_reporter->fail_state()); | 
|  |  | 
|  | {  // Pin section. | 
|  | DFAKE_SCOPED_LOCK(warner); | 
|  | EXPECT_FALSE(local_reporter->fail_state()); | 
|  | }  // Unpin section. | 
|  |  | 
|  | {  // Pin section. | 
|  | DFAKE_SCOPED_LOCK(warner); | 
|  | EXPECT_FALSE(local_reporter->fail_state()); | 
|  | { | 
|  | // Pin section again (not allowed by DFAKE_SCOPED_LOCK) | 
|  | DFAKE_SCOPED_LOCK(warner); | 
|  | EXPECT_NDEBUG_FALSE_DEBUG_TRUE(local_reporter->fail_state()); | 
|  | // Reset the status of warner for further tests. | 
|  | local_reporter->reset(); | 
|  | }  // Unpin section. | 
|  | }  // Unpin section. | 
|  |  | 
|  | { | 
|  | // Pin section. | 
|  | DFAKE_SCOPED_LOCK(warner); | 
|  | EXPECT_FALSE(local_reporter->fail_state()); | 
|  | }  // Unpin section. | 
|  | } | 
|  |  | 
|  | TEST(ThreadCollisionTest, MTBookCriticalSectionTest) { | 
|  | class NonThreadSafeQueue { | 
|  | public: | 
|  | explicit NonThreadSafeQueue(base::AsserterBase* asserter) | 
|  | : push_pop_(asserter) { | 
|  | } | 
|  |  | 
|  | void push(int value) { | 
|  | DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_); | 
|  | } | 
|  |  | 
|  | int pop() { | 
|  | DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | private: | 
|  | DFAKE_MUTEX(push_pop_); | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(NonThreadSafeQueue); | 
|  | }; | 
|  |  | 
|  | class QueueUser : public base::DelegateSimpleThread::Delegate { | 
|  | public: | 
|  | explicit QueueUser(NonThreadSafeQueue* queue) : queue_(queue) {} | 
|  |  | 
|  | void Run() override { | 
|  | queue_->push(0); | 
|  | queue_->pop(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | NonThreadSafeQueue* queue_; | 
|  | }; | 
|  |  | 
|  | AssertReporter* local_reporter = new AssertReporter(); | 
|  |  | 
|  | NonThreadSafeQueue queue(local_reporter); | 
|  |  | 
|  | QueueUser queue_user_a(&queue); | 
|  | QueueUser queue_user_b(&queue); | 
|  |  | 
|  | base::DelegateSimpleThread thread_a(&queue_user_a, "queue_user_thread_a"); | 
|  | base::DelegateSimpleThread thread_b(&queue_user_b, "queue_user_thread_b"); | 
|  |  | 
|  | thread_a.Start(); | 
|  | thread_b.Start(); | 
|  |  | 
|  | thread_a.Join(); | 
|  | thread_b.Join(); | 
|  |  | 
|  | EXPECT_NDEBUG_FALSE_DEBUG_TRUE(local_reporter->fail_state()); | 
|  | } | 
|  |  | 
|  | TEST(ThreadCollisionTest, MTScopedBookCriticalSectionTest) { | 
|  | // Queue with a 5 seconds push execution time, hopefuly the two used threads | 
|  | // in the test will enter the push at same time. | 
|  | class NonThreadSafeQueue { | 
|  | public: | 
|  | explicit NonThreadSafeQueue(base::AsserterBase* asserter) | 
|  | : push_pop_(asserter) { | 
|  | } | 
|  |  | 
|  | void push(int value) { | 
|  | DFAKE_SCOPED_LOCK(push_pop_); | 
|  | base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(5)); | 
|  | } | 
|  |  | 
|  | int pop() { | 
|  | DFAKE_SCOPED_LOCK(push_pop_); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | private: | 
|  | DFAKE_MUTEX(push_pop_); | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(NonThreadSafeQueue); | 
|  | }; | 
|  |  | 
|  | class QueueUser : public base::DelegateSimpleThread::Delegate { | 
|  | public: | 
|  | explicit QueueUser(NonThreadSafeQueue* queue) : queue_(queue) {} | 
|  |  | 
|  | void Run() override { | 
|  | queue_->push(0); | 
|  | queue_->pop(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | NonThreadSafeQueue* queue_; | 
|  | }; | 
|  |  | 
|  | AssertReporter* local_reporter = new AssertReporter(); | 
|  |  | 
|  | NonThreadSafeQueue queue(local_reporter); | 
|  |  | 
|  | QueueUser queue_user_a(&queue); | 
|  | QueueUser queue_user_b(&queue); | 
|  |  | 
|  | base::DelegateSimpleThread thread_a(&queue_user_a, "queue_user_thread_a"); | 
|  | base::DelegateSimpleThread thread_b(&queue_user_b, "queue_user_thread_b"); | 
|  |  | 
|  | thread_a.Start(); | 
|  | thread_b.Start(); | 
|  |  | 
|  | thread_a.Join(); | 
|  | thread_b.Join(); | 
|  |  | 
|  | EXPECT_NDEBUG_FALSE_DEBUG_TRUE(local_reporter->fail_state()); | 
|  | } | 
|  |  | 
|  | TEST(ThreadCollisionTest, MTSynchedScopedBookCriticalSectionTest) { | 
|  | // Queue with a 2 seconds push execution time, hopefuly the two used threads | 
|  | // in the test will enter the push at same time. | 
|  | class NonThreadSafeQueue { | 
|  | public: | 
|  | explicit NonThreadSafeQueue(base::AsserterBase* asserter) | 
|  | : push_pop_(asserter) { | 
|  | } | 
|  |  | 
|  | void push(int value) { | 
|  | DFAKE_SCOPED_LOCK(push_pop_); | 
|  | base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(2)); | 
|  | } | 
|  |  | 
|  | int pop() { | 
|  | DFAKE_SCOPED_LOCK(push_pop_); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | private: | 
|  | DFAKE_MUTEX(push_pop_); | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(NonThreadSafeQueue); | 
|  | }; | 
|  |  | 
|  | // This time the QueueUser class protects the non thread safe queue with | 
|  | // a lock. | 
|  | class QueueUser : public base::DelegateSimpleThread::Delegate { | 
|  | public: | 
|  | QueueUser(NonThreadSafeQueue* queue, base::Lock* lock) | 
|  | : queue_(queue), lock_(lock) {} | 
|  |  | 
|  | void Run() override { | 
|  | { | 
|  | base::AutoLock auto_lock(*lock_); | 
|  | queue_->push(0); | 
|  | } | 
|  | { | 
|  | base::AutoLock auto_lock(*lock_); | 
|  | queue_->pop(); | 
|  | } | 
|  | } | 
|  | private: | 
|  | NonThreadSafeQueue* queue_; | 
|  | base::Lock* lock_; | 
|  | }; | 
|  |  | 
|  | AssertReporter* local_reporter = new AssertReporter(); | 
|  |  | 
|  | NonThreadSafeQueue queue(local_reporter); | 
|  |  | 
|  | base::Lock lock; | 
|  |  | 
|  | QueueUser queue_user_a(&queue, &lock); | 
|  | QueueUser queue_user_b(&queue, &lock); | 
|  |  | 
|  | base::DelegateSimpleThread thread_a(&queue_user_a, "queue_user_thread_a"); | 
|  | base::DelegateSimpleThread thread_b(&queue_user_b, "queue_user_thread_b"); | 
|  |  | 
|  | thread_a.Start(); | 
|  | thread_b.Start(); | 
|  |  | 
|  | thread_a.Join(); | 
|  | thread_b.Join(); | 
|  |  | 
|  | EXPECT_FALSE(local_reporter->fail_state()); | 
|  | } | 
|  |  | 
|  | TEST(ThreadCollisionTest, MTSynchedScopedRecursiveBookCriticalSectionTest) { | 
|  | // Queue with a 2 seconds push execution time, hopefuly the two used threads | 
|  | // in the test will enter the push at same time. | 
|  | class NonThreadSafeQueue { | 
|  | public: | 
|  | explicit NonThreadSafeQueue(base::AsserterBase* asserter) | 
|  | : push_pop_(asserter) { | 
|  | } | 
|  |  | 
|  | void push(int) { | 
|  | DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); | 
|  | bar(); | 
|  | base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(2)); | 
|  | } | 
|  |  | 
|  | int pop() { | 
|  | DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void bar() { | 
|  | DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); | 
|  | } | 
|  |  | 
|  | private: | 
|  | DFAKE_MUTEX(push_pop_); | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(NonThreadSafeQueue); | 
|  | }; | 
|  |  | 
|  | // This time the QueueUser class protects the non thread safe queue with | 
|  | // a lock. | 
|  | class QueueUser : public base::DelegateSimpleThread::Delegate { | 
|  | public: | 
|  | QueueUser(NonThreadSafeQueue* queue, base::Lock* lock) | 
|  | : queue_(queue), lock_(lock) {} | 
|  |  | 
|  | void Run() override { | 
|  | { | 
|  | base::AutoLock auto_lock(*lock_); | 
|  | queue_->push(0); | 
|  | } | 
|  | { | 
|  | base::AutoLock auto_lock(*lock_); | 
|  | queue_->bar(); | 
|  | } | 
|  | { | 
|  | base::AutoLock auto_lock(*lock_); | 
|  | queue_->pop(); | 
|  | } | 
|  | } | 
|  | private: | 
|  | NonThreadSafeQueue* queue_; | 
|  | base::Lock* lock_; | 
|  | }; | 
|  |  | 
|  | AssertReporter* local_reporter = new AssertReporter(); | 
|  |  | 
|  | NonThreadSafeQueue queue(local_reporter); | 
|  |  | 
|  | base::Lock lock; | 
|  |  | 
|  | QueueUser queue_user_a(&queue, &lock); | 
|  | QueueUser queue_user_b(&queue, &lock); | 
|  |  | 
|  | base::DelegateSimpleThread thread_a(&queue_user_a, "queue_user_thread_a"); | 
|  | base::DelegateSimpleThread thread_b(&queue_user_b, "queue_user_thread_b"); | 
|  |  | 
|  | thread_a.Start(); | 
|  | thread_b.Start(); | 
|  |  | 
|  | thread_a.Join(); | 
|  | thread_b.Join(); | 
|  |  | 
|  | EXPECT_FALSE(local_reporter->fail_state()); | 
|  | } |