blob: e8b6bbc41d0945eb669c38d2211827326ad4ea4f [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <lib/unittest/unittest.h>
#include <kernel/mutex.h>
namespace {
template <typename MutexType>
bool mutex_lock_unlock() {
BEGIN_TEST;
MutexType mutex;
mutex.Acquire();
mutex.Release();
mutex.Acquire();
mutex.Release();
END_TEST;
}
template <typename MutexType>
bool mutex_is_held() {
BEGIN_TEST;
MutexType mutex;
EXPECT_FALSE(mutex.IsHeld(), "Lock not held");
mutex.Acquire();
EXPECT_TRUE(mutex.IsHeld(), "Lock held");
mutex.Release();
EXPECT_FALSE(mutex.IsHeld(), "Lock not held");
END_TEST;
}
template <typename MutexType>
bool mutex_assert_held() {
BEGIN_TEST;
MutexType mutex;
mutex.Acquire();
mutex.AssertHeld(); // Lock is held: this should be a no-op.
mutex.Release();
END_TEST;
}
// A struct with a guarded value.
template <typename MutexType>
struct ObjectWithLock {
MutexType mu;
int val TA_GUARDED(mu);
void TakeLock() TA_NO_THREAD_SAFETY_ANALYSIS { mu.Acquire(); }
};
template <typename MutexType>
bool mutex_assert_held_compile() {
BEGIN_TEST;
ObjectWithLock<MutexType> object;
// This shouldn't compile with thread analysis enabled.
#if defined(ENABLE_ERRORS)
object.val = 3;
#endif
// We take the lock, but Clang can't see it.
object.TakeLock();
// Without the assertion, Clang will object to setting "val".
#if !defined(ENABLE_ERRORS)
object.mu.AssertHeld();
#endif
object.val = 3;
// Without the assertion, Clang will object to releasing the lock.
object.mu.Release();
END_TEST;
}
DECLARE_SINGLETON_MUTEX(TestSingletonMutex);
// Ensure that acquiring a singleton mutex is thread-safe the first time it is
// acquired.
//
// We've previously had bugs where singleton mutexes were defined static and
// lazily initialised. While in general C++ guarantees that static variables
// are initialised in a thread-safe manner, the kernel turns off those
// mechanisms with the compiler flag "-fno-threadsafe-statics". This led to
// a bug where the first time a mutex was acquired, it could be help by
// multiple threads simultaneously.
//
// This test sets up N threads and races them acquiring the singleton mutex
// "TestSingletonMutex". While the test is safe to run multiple times, it
// can only exercise the static initialisation code path once per boot.
bool singleton_mutex_threadsafe() {
BEGIN_TEST;
// If we have already run print a warning that this test is unlikely to exercise anything.
static bool already_run = false;
if (already_run) {
dprintf(INFO,
"Test has already run this boot. "
"Subsequent runs will not exercise the mutex init code path again.\n");
}
already_run = true;
// Start multiple threads, all attempting to race to acquire the singleton mutex.
struct ThreadState {
ktl::atomic<bool> ready;
ktl::atomic<bool>* should_start;
ktl::atomic<bool>* in_critical_section;
Thread* thread;
};
auto worker_body = +[](void* arg) -> int {
ThreadState* state = static_cast<ThreadState*>(arg);
// Tell parent we are ready.
state->ready = true;
// Spin until all threads are ready to start.
//
// We busy-wait here without yielding to try and synchronise threads on
// different CPUs as much as possible, so that they all race to acquire
// the mutex below.
while (!state->should_start->load(ktl::memory_order_relaxed)) {
}
{
// Acquire the mutex.
Guard<Mutex> guard(TestSingletonMutex::Get());
// Ensure no other thread already has the mutex.
bool other_thread_in_critical_section = state->in_critical_section->exchange(true);
ZX_ASSERT_MSG(!other_thread_in_critical_section,
"Another thread was already in the critical section.");
// Delay before releasing the mutex, to give other threads a chance to
// notice we are holding it.
Thread::Current::SleepRelative(ZX_MSEC(1));
bool still_hold_critical_section = state->in_critical_section->exchange(false);
ZX_ASSERT_MSG(still_hold_critical_section, "Another thread released our critical section.");
}
return 0;
};
// Create worker theads and start them up.
constexpr int kNumThreads = 4;
std::array<ThreadState, kNumThreads> threads;
ktl::atomic<bool> should_start = false;
ktl::atomic<bool> in_critical_section = false;
for (ThreadState& state : threads) {
state.ready = false, state.should_start = &should_start,
state.in_critical_section = &in_critical_section,
state.thread = Thread::Create("test_singleton_mutex", worker_body, &state, DEFAULT_PRIORITY);
ASSERT_NONNULL(state.thread, "Thread::Create failed.");
state.thread->Resume();
}
// Wait for all the threads to start.
for (ThreadState& state : threads) {
while (!state.ready) {
Thread::Current::Yield();
}
}
// Let all the threads race.
should_start = true;
// Wait for all the threads to finish.
for (ThreadState& state : threads) {
int ret;
state.thread->Join(&ret, ZX_TIME_INFINITE);
}
END_TEST;
}
} // namespace
UNITTEST_START_TESTCASE(mutex_tests)
UNITTEST("mutex_lock_unlock", mutex_lock_unlock<Mutex>)
UNITTEST("mutex_is_held", mutex_is_held<Mutex>)
UNITTEST("mutex_assert_held", mutex_assert_held<Mutex>)
UNITTEST("mutex_assert_held_compile", mutex_assert_held_compile<Mutex>)
UNITTEST("critical_mutex_lock_unlock", mutex_lock_unlock<CriticalMutex>)
UNITTEST("critical_mutex_is_held", mutex_is_held<CriticalMutex>)
UNITTEST("critical_mutex_assert_held", mutex_assert_held<CriticalMutex>)
UNITTEST("critical_mutex_assert_held_compile", mutex_assert_held_compile<CriticalMutex>)
UNITTEST("singleton mutex has thread-safe init", singleton_mutex_threadsafe)
UNITTEST_END_TESTCASE(mutex_tests, "mutex", "Mutex tests")