// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "common/threading.h" #include #include #include #include #include #include #include static_assert(!std::is_copy_constructible_v); static_assert(!std::is_copy_assignable_v); static_assert(!std::is_copy_constructible_v); static_assert(!std::is_copy_assignable_v); static_assert(!std::is_copy_constructible_v); static_assert(!std::is_copy_assignable_v); TEST(ThreadingMutex, TryLock) { Threading::Mutex mutex; EXPECT_TRUE(mutex.try_lock()); bool acquired_in_thread = true; std::thread thread([&]() { acquired_in_thread = mutex.try_lock(); }); thread.join(); EXPECT_FALSE(acquired_in_thread); mutex.unlock(); EXPECT_TRUE(mutex.try_lock()); mutex.unlock(); } TEST(ThreadingMutex, StandardLockWrappers) { Threading::Mutex mutex; { const std::lock_guard lock(mutex); } { std::unique_lock lock(mutex); EXPECT_TRUE(lock.owns_lock()); } EXPECT_TRUE(mutex.try_lock()); mutex.unlock(); } TEST(ThreadingMutex, MutualExclusion) { static constexpr u32 NUM_THREADS = 4; static constexpr u32 NUM_INCREMENTS = 10000; Threading::Mutex mutex; u32 value = 0; std::vector threads; threads.reserve(NUM_THREADS); for (u32 i = 0; i < NUM_THREADS; i++) { threads.emplace_back([&mutex, &value]() { for (u32 j = 0; j < NUM_INCREMENTS; j++) { const std::lock_guard lock(mutex); value++; } }); } for (std::thread& thread : threads) thread.join(); EXPECT_EQ(value, NUM_THREADS * NUM_INCREMENTS); } TEST(ThreadingSharedMutex, TryLock) { Threading::SharedMutex mutex; EXPECT_TRUE(mutex.try_lock()); bool exclusive_locked = true; bool shared_locked = true; std::thread exclusive_test_thread([&]() { exclusive_locked = mutex.try_lock(); shared_locked = mutex.try_lock_shared(); }); exclusive_test_thread.join(); EXPECT_FALSE(exclusive_locked); EXPECT_FALSE(shared_locked); mutex.unlock(); EXPECT_TRUE(mutex.try_lock_shared()); std::thread shared_test_thread([&]() { shared_locked = mutex.try_lock_shared(); if (shared_locked) mutex.unlock_shared(); exclusive_locked = mutex.try_lock(); }); shared_test_thread.join(); EXPECT_TRUE(shared_locked); EXPECT_FALSE(exclusive_locked); mutex.unlock_shared(); EXPECT_TRUE(mutex.try_lock()); mutex.unlock(); } TEST(ThreadingSharedMutex, StandardLockWrappers) { Threading::SharedMutex mutex; { const std::lock_guard lock(mutex); } { std::unique_lock lock(mutex); EXPECT_TRUE(lock.owns_lock()); } { std::shared_lock lock(mutex); EXPECT_TRUE(lock.owns_lock()); } } TEST(ThreadingSharedMutex, MutualExclusion) { static constexpr u32 NUM_THREADS = 4; static constexpr u32 NUM_INCREMENTS = 10000; Threading::SharedMutex mutex; u32 value = 0; std::vector threads; threads.reserve(NUM_THREADS); for (u32 i = 0; i < NUM_THREADS; i++) { threads.emplace_back([&mutex, &value]() { for (u32 j = 0; j < NUM_INCREMENTS; j++) { const std::lock_guard lock(mutex); value++; } }); } for (std::thread& thread : threads) thread.join(); EXPECT_EQ(value, NUM_THREADS * NUM_INCREMENTS); } TEST(ThreadingConditionVariable, NotifyOneAndPredicateWait) { Threading::Mutex mutex; Threading::ConditionVariable condition; Threading::KernelSemaphore waiting; bool wake = false; bool woke = false; std::thread thread([&]() { std::unique_lock lock(mutex); waiting.Post(); condition.wait(lock, [&wake]() { return wake; }); woke = true; }); waiting.Wait(); { const std::lock_guard lock(mutex); EXPECT_FALSE(woke); wake = true; } condition.notify_one(); thread.join(); EXPECT_TRUE(woke); } TEST(ThreadingConditionVariable, NotifyAll) { static constexpr u32 NUM_THREADS = 4; Threading::Mutex mutex; Threading::ConditionVariable condition; Threading::KernelSemaphore waiting; bool wake = false; u32 num_woken = 0; std::vector threads; threads.reserve(NUM_THREADS); for (u32 i = 0; i < NUM_THREADS; i++) { threads.emplace_back([&]() { std::unique_lock lock(mutex); waiting.Post(); condition.wait(lock, [&wake]() { return wake; }); num_woken++; }); } for (u32 i = 0; i < NUM_THREADS; i++) waiting.Wait(); { const std::lock_guard lock(mutex); wake = true; } condition.notify_all(); for (std::thread& thread : threads) thread.join(); EXPECT_EQ(num_woken, NUM_THREADS); } TEST(ThreadingKernelSemaphore, PostWaitAndTryWait) { Threading::KernelSemaphore semaphore; EXPECT_FALSE(semaphore.TryWait()); semaphore.Post(); EXPECT_TRUE(semaphore.TryWait()); EXPECT_FALSE(semaphore.TryWait()); Threading::KernelSemaphore started; std::atomic_bool finished = false; std::thread thread([&]() { started.Post(); semaphore.Wait(); finished.store(true, std::memory_order_release); }); started.Wait(); EXPECT_FALSE(finished.load(std::memory_order_acquire)); semaphore.Post(); thread.join(); EXPECT_TRUE(finished.load(std::memory_order_acquire)); }