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