| // 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()); | 
 | } |