blob: 90656ca5844a98a56d58202933dc16c78de4426c [file] [log] [blame]
// 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 "build/build_config.h"
#include "mojo/edk/platform/thread_utils.h"
#include "mojo/edk/system/test/timeouts.h"
#include "testing/gtest/include/gtest/gtest.h"
using mojo::platform::ThreadSleep;
using mojo::system::test::DeadlineFromMilliseconds;
namespace mojo {
namespace util {
namespace {
// Sleeps for a "very small" amount of time.
void EpsilonRandomSleep() {
ThreadSleep(DeadlineFromMilliseconds(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);
}
#if defined(OS_ANDROID)
// TODO(vtl): On Android, death tests don't seem to work properly with
// |assert()| (which presumably calls |abort()|).
#define MAYBE_AssertHeld DISABLED_AssertHeld
#else
#define MAYBE_AssertHeld AssertHeld
#endif
TEST(MutexTest, MAYBE_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