diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index 37ac7d6c7..d5e835893 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -280,8 +280,156 @@ TEST_CASE("Wait on Multiple Events", "Event") { } TEST_CASE("Wait on Semaphore", "Semaphore") { - // TODO(bwrsandman): - REQUIRE(true); + WaitResult result; + std::unique_ptr sem; + int previous_count = 0; + + // Wait on semaphore with no room + sem = Semaphore::Create(0, 5); + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kTimeout); + + // Add room in semaphore + REQUIRE(sem->Release(2, &previous_count)); + REQUIRE(previous_count == 0); + REQUIRE(sem->Release(1, &previous_count)); + REQUIRE(previous_count == 2); + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kSuccess); + REQUIRE(sem->Release(1, &previous_count)); + REQUIRE(previous_count == 2); + + // Set semaphore over maximum_count + sem = Semaphore::Create(5, 5); + previous_count = -1; + REQUIRE_FALSE(sem->Release(1, &previous_count)); + REQUIRE(previous_count == -1); + REQUIRE_FALSE(sem->Release(10, &previous_count)); + REQUIRE(previous_count == -1); + sem = Semaphore::Create(0, 5); + REQUIRE_FALSE(sem->Release(10, &previous_count)); + REQUIRE(previous_count == -1); + REQUIRE_FALSE(sem->Release(10, &previous_count)); + REQUIRE(previous_count == -1); + + // Test invalid Release parameters + REQUIRE_FALSE(sem->Release(0, &previous_count)); + REQUIRE(previous_count == -1); + REQUIRE_FALSE(sem->Release(-1, &previous_count)); + REQUIRE(previous_count == -1); + + // Wait on fully available semaphore + sem = Semaphore::Create(5, 5); + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kSuccess); + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kSuccess); + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kSuccess); + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kSuccess); + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kSuccess); + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kTimeout); + + // Semaphore between threads + sem = Semaphore::Create(5, 5); + Sleep(10ms); + // Occupy the semaphore with 5 threads + auto func = [&sem] { + auto res = Wait(sem.get(), false, 100ms); + Sleep(500ms); + if (res == WaitResult::kSuccess) { + sem->Release(1, nullptr); + } + }; + auto threads = std::array{ + std::thread(func), std::thread(func), std::thread(func), + std::thread(func), std::thread(func), + }; + // Give threads time to acquire semaphore + Sleep(10ms); + // Attempt to acquire full semaphore with current (6th) thread + result = Wait(sem.get(), false, 20ms); + REQUIRE(result == WaitResult::kTimeout); + // Give threads time to release semaphore + for (auto& t : threads) { + t.join(); + } + result = Wait(sem.get(), false, 10ms); + REQUIRE(result == WaitResult::kSuccess); + sem->Release(1, &previous_count); + REQUIRE(previous_count == 4); + + // Test invalid construction parameters + // These are invalid according to documentation + // TODO(bwrsandman): Many of these invalid invocations succeed + sem = Semaphore::Create(-1, 5); + // REQUIRE(sem.get() == nullptr); + sem = Semaphore::Create(10, 5); + // REQUIRE(sem.get() == nullptr); + sem = Semaphore::Create(0, 0); + // REQUIRE(sem.get() == nullptr); + sem = Semaphore::Create(0, -1); + // REQUIRE(sem.get() == nullptr); +} + +TEST_CASE("Wait on Multiple Semaphores", "Semaphore") { + WaitResult all_result; + std::pair any_result; + int previous_count; + std::unique_ptr sem0, sem1; + + // Test Wait all which should fail + sem0 = Semaphore::Create(0, 5); + sem1 = Semaphore::Create(5, 5); + all_result = WaitAll({sem0.get(), sem1.get()}, false, 10ms); + REQUIRE(all_result == WaitResult::kTimeout); + previous_count = -1; + REQUIRE(sem0->Release(1, &previous_count)); + REQUIRE(previous_count == 0); + previous_count = -1; + REQUIRE_FALSE(sem1->Release(1, &previous_count)); + REQUIRE(previous_count == -1); + + // Test Wait all again which should succeed + sem0 = Semaphore::Create(1, 5); + sem1 = Semaphore::Create(5, 5); + all_result = WaitAll({sem0.get(), sem1.get()}, false, 10ms); + REQUIRE(all_result == WaitResult::kSuccess); + previous_count = -1; + REQUIRE(sem0->Release(1, &previous_count)); + REQUIRE(previous_count == 0); + previous_count = -1; + REQUIRE(sem1->Release(1, &previous_count)); + REQUIRE(previous_count == 4); + + // Test Wait Any which should fail + sem0 = Semaphore::Create(0, 5); + sem1 = Semaphore::Create(0, 5); + any_result = WaitAny({sem0.get(), sem1.get()}, false, 10ms); + REQUIRE(any_result.first == WaitResult::kTimeout); + REQUIRE(any_result.second == 0); + previous_count = -1; + REQUIRE(sem0->Release(1, &previous_count)); + REQUIRE(previous_count == 0); + previous_count = -1; + REQUIRE(sem1->Release(1, &previous_count)); + REQUIRE(previous_count == 0); + + // Test Wait Any which should succeed + sem0 = Semaphore::Create(0, 5); + sem1 = Semaphore::Create(5, 5); + any_result = WaitAny({sem0.get(), sem1.get()}, false, 10ms); + REQUIRE(any_result.first == WaitResult::kSuccess); + REQUIRE(any_result.second == 1); + previous_count = -1; + REQUIRE(sem0->Release(1, &previous_count)); + REQUIRE(previous_count == 0); + previous_count = -1; + REQUIRE(sem1->Release(1, &previous_count)); + REQUIRE(previous_count == 4); } TEST_CASE("Wait on Mutant", "Mutant") { diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index 6c0d10e7c..bdd37e8b2 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -290,6 +290,33 @@ class PosixCondition : public PosixConditionBase { const bool manual_reset_; }; +template <> +class PosixCondition : public PosixConditionBase { + public: + PosixCondition(uint32_t initial_count, uint32_t maximum_count) + : count_(initial_count), maximum_count_(maximum_count) {} + + bool Release(uint32_t release_count, int* out_previous_count) { + if (maximum_count_ - count_ >= release_count) { + auto lock = std::unique_lock(mutex_); + if (out_previous_count) *out_previous_count = count_; + count_ += release_count; + cond_.notify_all(); + return true; + } + return false; + } + + private: + inline bool signaled() const override { return count_ > 0; } + inline void post_execution() override { + count_--; + cond_.notify_all(); + } + uint32_t count_; + const uint32_t maximum_count_; +}; + // Native posix thread handle template class PosixThreadHandle : public T { @@ -311,6 +338,7 @@ template class PosixConditionHandle : public T { public: PosixConditionHandle(bool manual_reset, bool initial_state); + PosixConditionHandle(uint32_t initial_count, uint32_t maximum_count); ~PosixConditionHandle() override = default; protected: @@ -322,9 +350,9 @@ class PosixConditionHandle : public T { }; template <> -PosixConditionHandle::PosixConditionHandle(bool manual_reset, - bool initial_state) - : handle_() {} +PosixConditionHandle::PosixConditionHandle(uint32_t initial_count, + uint32_t maximum_count) + : handle_(initial_count, maximum_count) {} template <> PosixConditionHandle::PosixConditionHandle(bool manual_reset, @@ -394,17 +422,18 @@ std::unique_ptr Event::CreateAutoResetEvent(bool initial_state) { return std::make_unique(false, initial_state); } -// TODO(dougvj) class PosixSemaphore : public PosixConditionHandle { public: PosixSemaphore(int initial_count, int maximum_count) - : PosixConditionHandle(false, false) { - assert_always(); - } + : PosixConditionHandle(static_cast(initial_count), + static_cast(maximum_count)) {} ~PosixSemaphore() override = default; bool Release(int release_count, int* out_previous_count) override { - assert_always(); - return false; + if (release_count < 1) { + return false; + } + return handle_.Release(static_cast(release_count), + out_previous_count); } };