|  | // Copyright 2015 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 "mojo/edk/util/mutex.h" | 
|  |  | 
|  | #include <stdlib.h> | 
|  |  | 
|  | #include <thread> | 
|  |  | 
|  | #include "mojo/edk/system/test/sleep.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace mojo { | 
|  | namespace util { | 
|  | namespace { | 
|  |  | 
|  | // Sleeps for a "very small" amount of time. | 
|  | void EpsilonRandomSleep() { | 
|  | system::test::SleepMilliseconds(static_cast<unsigned>(rand()) % 20u); | 
|  | } | 
|  |  | 
|  | // Basic test to make sure that Lock()/Unlock()/TryLock() don't crash ---------- | 
|  |  | 
|  | TEST(MutexTest, Basic) { | 
|  | Mutex mutex; | 
|  |  | 
|  | int thread_acquired = 0; | 
|  | auto thread = std::thread([&mutex, &thread_acquired]() { | 
|  | for (int i = 0; i < 10; i++) { | 
|  | mutex.Lock(); | 
|  | mutex.AssertHeld(); | 
|  | thread_acquired++; | 
|  | mutex.Unlock(); | 
|  | } | 
|  | for (int i = 0; i < 10; i++) { | 
|  | mutex.Lock(); | 
|  | mutex.AssertHeld(); | 
|  | thread_acquired++; | 
|  | EpsilonRandomSleep(); | 
|  | mutex.Unlock(); | 
|  | } | 
|  | for (int i = 0; i < 10; i++) { | 
|  | if (mutex.TryLock()) { | 
|  | mutex.AssertHeld(); | 
|  | thread_acquired++; | 
|  | EpsilonRandomSleep(); | 
|  | mutex.Unlock(); | 
|  | } | 
|  | } | 
|  | }); | 
|  |  | 
|  | int acquired = 0; | 
|  | for (int i = 0; i < 5; i++) { | 
|  | mutex.Lock(); | 
|  | mutex.AssertHeld(); | 
|  | acquired++; | 
|  | mutex.Unlock(); | 
|  | } | 
|  | for (int i = 0; i < 10; i++) { | 
|  | mutex.Lock(); | 
|  | mutex.AssertHeld(); | 
|  | acquired++; | 
|  | EpsilonRandomSleep(); | 
|  | mutex.Unlock(); | 
|  | } | 
|  | for (int i = 0; i < 10; i++) { | 
|  | if (mutex.TryLock()) { | 
|  | mutex.AssertHeld(); | 
|  | acquired++; | 
|  | EpsilonRandomSleep(); | 
|  | mutex.Unlock(); | 
|  | } | 
|  | } | 
|  | for (int i = 0; i < 5; i++) { | 
|  | mutex.Lock(); | 
|  | mutex.AssertHeld(); | 
|  | acquired++; | 
|  | EpsilonRandomSleep(); | 
|  | mutex.Unlock(); | 
|  | } | 
|  |  | 
|  | thread.join(); | 
|  |  | 
|  | EXPECT_GE(acquired, 20); | 
|  | EXPECT_GE(thread_acquired, 20); | 
|  | } | 
|  |  | 
|  | TEST(MutexTest, AssertHeld) { | 
|  | Mutex mutex; | 
|  |  | 
|  | #if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) | 
|  | // For non-Debug builds, |AssertHeld()| should do nothing. | 
|  | mutex.AssertHeld(); | 
|  | #else | 
|  | EXPECT_DEATH_IF_SUPPORTED({ mutex.AssertHeld(); }, "pthread_mutex_lock"); | 
|  | #endif  // defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) | 
|  |  | 
|  | // TODO(vtl): Should also test the case when the mutex is held by another | 
|  | // thread, though this is more annoying since it requires synchronization. | 
|  | } | 
|  |  | 
|  | // Test that TryLock() works as expected --------------------------------------- | 
|  |  | 
|  | TEST(MutexTest, TryLock) MOJO_NO_THREAD_SAFETY_ANALYSIS { | 
|  | Mutex mutex; | 
|  |  | 
|  | ASSERT_TRUE(mutex.TryLock()); | 
|  | // We now have the mutex.... | 
|  |  | 
|  | { | 
|  | // This thread will not be able to get the mutex. | 
|  | auto thread = std::thread([&mutex]() { EXPECT_FALSE(mutex.TryLock()); }); | 
|  | thread.join(); | 
|  | } | 
|  |  | 
|  | mutex.Unlock(); | 
|  | // And now we don't. | 
|  |  | 
|  | { | 
|  | // This thread will be able to get it (and then release it). | 
|  | auto thread = std::thread([&mutex]() { | 
|  | EXPECT_TRUE(mutex.TryLock()); | 
|  | mutex.AssertHeld(); | 
|  | mutex.Unlock(); | 
|  | }); | 
|  | thread.join(); | 
|  |  | 
|  | // And we can take it again. | 
|  | ASSERT_TRUE(mutex.TryLock()); | 
|  | } | 
|  |  | 
|  | mutex.Unlock(); | 
|  | } | 
|  |  | 
|  | // Tests that mutexes actually exclude ----------------------------------------- | 
|  |  | 
|  | // We'll call this from both the main thread and secondary threads. | 
|  | void DoStuffWithMutex(Mutex* mutex, int* value) { | 
|  | for (int i = 0; i < 40; i++) { | 
|  | mutex->Lock(); | 
|  | int v = *value; | 
|  | EpsilonRandomSleep(); | 
|  | *value = v + 1; | 
|  | mutex->Unlock(); | 
|  | } | 
|  | } | 
|  |  | 
|  | std::thread MakeMutexTestThread(Mutex* mutex, int* value) { | 
|  | return std::thread([mutex, value]() { DoStuffWithMutex(mutex, value); }); | 
|  | } | 
|  |  | 
|  | TEST(MutexTest, MutexTwoThreads) { | 
|  | Mutex mutex; | 
|  | int value = 0; | 
|  |  | 
|  | std::thread thread = MakeMutexTestThread(&mutex, &value); | 
|  |  | 
|  | DoStuffWithMutex(&mutex, &value); | 
|  |  | 
|  | thread.join(); | 
|  |  | 
|  | EXPECT_EQ(2 * 40, value); | 
|  | } | 
|  |  | 
|  | TEST(MutexTest, MutexFourThreads) { | 
|  | Mutex mutex; | 
|  | int value = 0; | 
|  |  | 
|  | std::thread thread1 = MakeMutexTestThread(&mutex, &value); | 
|  | std::thread thread2 = MakeMutexTestThread(&mutex, &value); | 
|  | std::thread thread3 = MakeMutexTestThread(&mutex, &value); | 
|  |  | 
|  | DoStuffWithMutex(&mutex, &value); | 
|  |  | 
|  | thread1.join(); | 
|  | thread2.join(); | 
|  | thread3.join(); | 
|  |  | 
|  | EXPECT_EQ(4 * 40, value); | 
|  | } | 
|  |  | 
|  | // MutexLocker ----------------------------------------------------------------- | 
|  |  | 
|  | TEST(MutexTest, MutexLocker) { | 
|  | Mutex mutex; | 
|  |  | 
|  | { | 
|  | MutexLocker locker(&mutex); | 
|  | mutex.AssertHeld(); | 
|  | } | 
|  |  | 
|  | // The destruction of |locker| should unlock |mutex|. | 
|  | ASSERT_TRUE(mutex.TryLock()); | 
|  | mutex.AssertHeld(); | 
|  | mutex.Unlock(); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace util | 
|  | }  // namespace mojo |