|  | // 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/synchronization/lock.h" | 
|  |  | 
|  | #include <stdlib.h> | 
|  |  | 
|  | #include "base/compiler_specific.h" | 
|  | #include "base/debug/activity_tracker.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/threading/platform_thread.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace base { | 
|  |  | 
|  | // Basic test to make sure that Acquire()/Release()/Try() don't crash ---------- | 
|  |  | 
|  | class BasicLockTestThread : public PlatformThread::Delegate { | 
|  | public: | 
|  | explicit BasicLockTestThread(Lock* lock) : lock_(lock), acquired_(0) {} | 
|  |  | 
|  | void ThreadMain() override { | 
|  | for (int i = 0; i < 10; i++) { | 
|  | lock_->Acquire(); | 
|  | acquired_++; | 
|  | lock_->Release(); | 
|  | } | 
|  | for (int i = 0; i < 10; i++) { | 
|  | lock_->Acquire(); | 
|  | acquired_++; | 
|  | PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); | 
|  | lock_->Release(); | 
|  | } | 
|  | for (int i = 0; i < 10; i++) { | 
|  | if (lock_->Try()) { | 
|  | acquired_++; | 
|  | PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); | 
|  | lock_->Release(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | int acquired() const { return acquired_; } | 
|  |  | 
|  | private: | 
|  | Lock* lock_; | 
|  | int acquired_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(BasicLockTestThread); | 
|  | }; | 
|  |  | 
|  | TEST(LockTest, Basic) { | 
|  | Lock lock; | 
|  | BasicLockTestThread thread(&lock); | 
|  | PlatformThreadHandle handle; | 
|  |  | 
|  | ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); | 
|  |  | 
|  | int acquired = 0; | 
|  | for (int i = 0; i < 5; i++) { | 
|  | lock.Acquire(); | 
|  | acquired++; | 
|  | lock.Release(); | 
|  | } | 
|  | for (int i = 0; i < 10; i++) { | 
|  | lock.Acquire(); | 
|  | acquired++; | 
|  | PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); | 
|  | lock.Release(); | 
|  | } | 
|  | for (int i = 0; i < 10; i++) { | 
|  | if (lock.Try()) { | 
|  | acquired++; | 
|  | PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); | 
|  | lock.Release(); | 
|  | } | 
|  | } | 
|  | for (int i = 0; i < 5; i++) { | 
|  | lock.Acquire(); | 
|  | acquired++; | 
|  | PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 20)); | 
|  | lock.Release(); | 
|  | } | 
|  |  | 
|  | PlatformThread::Join(handle); | 
|  |  | 
|  | EXPECT_GE(acquired, 20); | 
|  | EXPECT_GE(thread.acquired(), 20); | 
|  | } | 
|  |  | 
|  | // Test that Try() works as expected ------------------------------------------- | 
|  |  | 
|  | class TryLockTestThread : public PlatformThread::Delegate { | 
|  | public: | 
|  | explicit TryLockTestThread(Lock* lock) : lock_(lock), got_lock_(false) {} | 
|  |  | 
|  | void ThreadMain() override { | 
|  | got_lock_ = lock_->Try(); | 
|  | if (got_lock_) | 
|  | lock_->Release(); | 
|  | } | 
|  |  | 
|  | bool got_lock() const { return got_lock_; } | 
|  |  | 
|  | private: | 
|  | Lock* lock_; | 
|  | bool got_lock_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(TryLockTestThread); | 
|  | }; | 
|  |  | 
|  | TEST(LockTest, TryLock) { | 
|  | Lock lock; | 
|  |  | 
|  | ASSERT_TRUE(lock.Try()); | 
|  | // We now have the lock.... | 
|  |  | 
|  | // This thread will not be able to get the lock. | 
|  | { | 
|  | TryLockTestThread thread(&lock); | 
|  | PlatformThreadHandle handle; | 
|  |  | 
|  | ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); | 
|  |  | 
|  | PlatformThread::Join(handle); | 
|  |  | 
|  | ASSERT_FALSE(thread.got_lock()); | 
|  | } | 
|  |  | 
|  | lock.Release(); | 
|  |  | 
|  | // This thread will.... | 
|  | { | 
|  | TryLockTestThread thread(&lock); | 
|  | PlatformThreadHandle handle; | 
|  |  | 
|  | ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); | 
|  |  | 
|  | PlatformThread::Join(handle); | 
|  |  | 
|  | ASSERT_TRUE(thread.got_lock()); | 
|  | // But it released it.... | 
|  | ASSERT_TRUE(lock.Try()); | 
|  | } | 
|  |  | 
|  | lock.Release(); | 
|  | } | 
|  |  | 
|  | TEST(LockTest, TryTrackedLock) { | 
|  | // Enable the activity tracker. | 
|  | debug::GlobalActivityTracker::CreateWithLocalMemory(64 << 10, 0, "", 3, 0); | 
|  |  | 
|  | Lock lock; | 
|  |  | 
|  | ASSERT_TRUE(lock.Try()); | 
|  | // We now have the lock.... | 
|  |  | 
|  | // This thread will not be able to get the lock. | 
|  | { | 
|  | TryLockTestThread thread(&lock); | 
|  | PlatformThreadHandle handle; | 
|  |  | 
|  | ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); | 
|  |  | 
|  | PlatformThread::Join(handle); | 
|  |  | 
|  | ASSERT_FALSE(thread.got_lock()); | 
|  | } | 
|  |  | 
|  | lock.Release(); | 
|  |  | 
|  | // This thread will.... | 
|  | { | 
|  | TryLockTestThread thread(&lock); | 
|  | PlatformThreadHandle handle; | 
|  |  | 
|  | ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); | 
|  |  | 
|  | PlatformThread::Join(handle); | 
|  |  | 
|  | ASSERT_TRUE(thread.got_lock()); | 
|  | // But it released it.... | 
|  | ASSERT_TRUE(lock.Try()); | 
|  | } | 
|  |  | 
|  | lock.Release(); | 
|  | debug::GlobalActivityTracker::ReleaseForTesting(); | 
|  | } | 
|  |  | 
|  | // Tests that locks actually exclude ------------------------------------------- | 
|  |  | 
|  | class MutexLockTestThread : public PlatformThread::Delegate { | 
|  | public: | 
|  | MutexLockTestThread(Lock* lock, int* value) : lock_(lock), value_(value) {} | 
|  |  | 
|  | // Static helper which can also be called from the main thread. | 
|  | static void DoStuff(Lock* lock, int* value) { | 
|  | for (int i = 0; i < 40; i++) { | 
|  | lock->Acquire(); | 
|  | int v = *value; | 
|  | PlatformThread::Sleep(TimeDelta::FromMilliseconds(rand() % 10)); | 
|  | *value = v + 1; | 
|  | lock->Release(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ThreadMain() override { DoStuff(lock_, value_); } | 
|  |  | 
|  | private: | 
|  | Lock* lock_; | 
|  | int* value_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(MutexLockTestThread); | 
|  | }; | 
|  |  | 
|  | TEST(LockTest, MutexTwoThreads) { | 
|  | Lock lock; | 
|  | int value = 0; | 
|  |  | 
|  | MutexLockTestThread thread(&lock, &value); | 
|  | PlatformThreadHandle handle; | 
|  |  | 
|  | ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle)); | 
|  |  | 
|  | MutexLockTestThread::DoStuff(&lock, &value); | 
|  |  | 
|  | PlatformThread::Join(handle); | 
|  |  | 
|  | EXPECT_EQ(2 * 40, value); | 
|  | } | 
|  |  | 
|  | TEST(LockTest, MutexFourThreads) { | 
|  | Lock lock; | 
|  | int value = 0; | 
|  |  | 
|  | MutexLockTestThread thread1(&lock, &value); | 
|  | MutexLockTestThread thread2(&lock, &value); | 
|  | MutexLockTestThread thread3(&lock, &value); | 
|  | PlatformThreadHandle handle1; | 
|  | PlatformThreadHandle handle2; | 
|  | PlatformThreadHandle handle3; | 
|  |  | 
|  | ASSERT_TRUE(PlatformThread::Create(0, &thread1, &handle1)); | 
|  | ASSERT_TRUE(PlatformThread::Create(0, &thread2, &handle2)); | 
|  | ASSERT_TRUE(PlatformThread::Create(0, &thread3, &handle3)); | 
|  |  | 
|  | MutexLockTestThread::DoStuff(&lock, &value); | 
|  |  | 
|  | PlatformThread::Join(handle1); | 
|  | PlatformThread::Join(handle2); | 
|  | PlatformThread::Join(handle3); | 
|  |  | 
|  | EXPECT_EQ(4 * 40, value); | 
|  | } | 
|  |  | 
|  | }  // namespace base |