From 331bb0ea9ab81d76e4175a55ea03a5c330838ca9 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Sun, 9 Dec 2018 15:44:44 -0800 Subject: [PATCH] [threading linux] Implement Mutant Keep track of recursive locks with owner and count of locks. Only allow recursive locks from same thread and increment count. Only allow first locks from when count is zero. Test acquiring and releasing mutant on same and on different threads. Test Release return values. Test WaitAll and WaitAny. --- src/xenia/base/testing/threading_test.cc | 119 ++++++++++++++++++++++- src/xenia/base/threading_posix.cc | 53 +++++++--- 2 files changed, 158 insertions(+), 14 deletions(-) diff --git a/src/xenia/base/testing/threading_test.cc b/src/xenia/base/testing/threading_test.cc index d5e835893..f35d647dd 100644 --- a/src/xenia/base/testing/threading_test.cc +++ b/src/xenia/base/testing/threading_test.cc @@ -433,8 +433,123 @@ TEST_CASE("Wait on Multiple Semaphores", "Semaphore") { } TEST_CASE("Wait on Mutant", "Mutant") { - // TODO(bwrsandman): - REQUIRE(true); + WaitResult result; + std::unique_ptr mut; + + // Release on initially owned mutant + mut = Mutant::Create(true); + REQUIRE(mut->Release()); + REQUIRE_FALSE(mut->Release()); + + // Release on initially not-owned mutant + mut = Mutant::Create(false); + REQUIRE_FALSE(mut->Release()); + + // Wait on initially owned mutant + mut = Mutant::Create(true); + result = Wait(mut.get(), false, 1ms); + REQUIRE(result == WaitResult::kSuccess); + REQUIRE(mut->Release()); + REQUIRE(mut->Release()); + REQUIRE_FALSE(mut->Release()); + + // Wait on initially not owned mutant + mut = Mutant::Create(false); + result = Wait(mut.get(), false, 1ms); + REQUIRE(result == WaitResult::kSuccess); + REQUIRE(mut->Release()); + REQUIRE_FALSE(mut->Release()); + + // Multiple waits (or locks) + mut = Mutant::Create(false); + for (int i = 0; i < 10; ++i) { + result = Wait(mut.get(), false, 1ms); + REQUIRE(result == WaitResult::kSuccess); + } + for (int i = 0; i < 10; ++i) { + REQUIRE(mut->Release()); + } + REQUIRE_FALSE(mut->Release()); + + // Test mutants on other threads + auto thread1 = std::thread([&mut] { + Sleep(5ms); + mut = Mutant::Create(true); + Sleep(100ms); + mut->Release(); + }); + Sleep(10ms); + REQUIRE_FALSE(mut->Release()); + Sleep(10ms); + result = Wait(mut.get(), false, 50ms); + REQUIRE(result == WaitResult::kTimeout); + thread1.join(); + result = Wait(mut.get(), false, 1ms); + REQUIRE(result == WaitResult::kSuccess); + REQUIRE(mut->Release()); +} + +TEST_CASE("Wait on Multiple Mutants", "Mutant") { + WaitResult all_result; + std::pair any_result; + std::unique_ptr mut0, mut1; + + // Test which should fail for WaitAll and WaitAny + auto thread0 = std::thread([&mut0, &mut1] { + mut0 = Mutant::Create(true); + mut1 = Mutant::Create(true); + Sleep(50ms); + mut0->Release(); + mut1->Release(); + }); + Sleep(10ms); + all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms); + REQUIRE(all_result == WaitResult::kTimeout); + REQUIRE_FALSE(mut0->Release()); + REQUIRE_FALSE(mut1->Release()); + any_result = WaitAny({mut0.get(), mut1.get()}, false, 10ms); + REQUIRE(any_result.first == WaitResult::kTimeout); + REQUIRE(any_result.second == 0); + REQUIRE_FALSE(mut0->Release()); + REQUIRE_FALSE(mut1->Release()); + thread0.join(); + + // Test which should fail for WaitAll but not WaitAny + auto thread1 = std::thread([&mut0, &mut1] { + mut0 = Mutant::Create(true); + mut1 = Mutant::Create(false); + Sleep(50ms); + mut0->Release(); + }); + Sleep(10ms); + all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms); + REQUIRE(all_result == WaitResult::kTimeout); + REQUIRE_FALSE(mut0->Release()); + REQUIRE_FALSE(mut1->Release()); + any_result = WaitAny({mut0.get(), mut1.get()}, false, 10ms); + REQUIRE(any_result.first == WaitResult::kSuccess); + REQUIRE(any_result.second == 1); + REQUIRE_FALSE(mut0->Release()); + REQUIRE(mut1->Release()); + thread1.join(); + + // Test which should pass for WaitAll and WaitAny + auto thread2 = std::thread([&mut0, &mut1] { + mut0 = Mutant::Create(false); + mut1 = Mutant::Create(false); + Sleep(50ms); + }); + Sleep(10ms); + all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms); + REQUIRE(all_result == WaitResult::kSuccess); + REQUIRE(mut0->Release()); + REQUIRE(mut1->Release()); + any_result = WaitAny({mut0.get(), mut1.get()}, false, 10ms); + REQUIRE(any_result.first == WaitResult::kSuccess); + REQUIRE(any_result.second == 0); + REQUIRE(mut0->Release()); + REQUIRE_FALSE(mut1->Release()); + thread2.join(); } TEST_CASE("Create and Trigger Timer", "Timer") { diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index bdd37e8b2..771154136 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -317,6 +317,40 @@ class PosixCondition : public PosixConditionBase { const uint32_t maximum_count_; }; +template <> +class PosixCondition : public PosixConditionBase { + public: + explicit PosixCondition(bool initial_owner) : count_(0) { + if (initial_owner) { + count_ = 1; + owner_ = std::this_thread::get_id(); + } + } + bool Release() { + if (owner_ == std::this_thread::get_id() && count_ > 0) { + auto lock = std::unique_lock(mutex_); + --count_; + // Free to be acquired by another thread + if (count_ == 0) { + cond_.notify_one(); + } + return true; + } + return false; + } + + private: + inline bool signaled() const override { + return count_ == 0 || owner_ == std::this_thread::get_id(); + } + inline void post_execution() override { + count_++; + owner_ = std::this_thread::get_id(); + } + uint32_t count_; + std::thread::id owner_; +}; + // Native posix thread handle template class PosixThreadHandle : public T { @@ -337,6 +371,7 @@ class PosixThreadHandle : public T { template class PosixConditionHandle : public T { public: + explicit PosixConditionHandle(bool initial_owner); PosixConditionHandle(bool manual_reset, bool initial_state); PosixConditionHandle(uint32_t initial_count, uint32_t maximum_count); ~PosixConditionHandle() override = default; @@ -355,9 +390,8 @@ PosixConditionHandle::PosixConditionHandle(uint32_t initial_count, : handle_(initial_count, maximum_count) {} template <> -PosixConditionHandle::PosixConditionHandle(bool manual_reset, - bool initial_state) - : handle_() {} +PosixConditionHandle::PosixConditionHandle(bool initial_owner) + : handle_(initial_owner) {} template <> PosixConditionHandle::PosixConditionHandle(bool manual_reset, @@ -442,17 +476,12 @@ std::unique_ptr Semaphore::Create(int initial_count, return std::make_unique(initial_count, maximum_count); } -// TODO(dougvj) class PosixMutant : public PosixConditionHandle { public: - PosixMutant(bool initial_owner) : PosixConditionHandle(false, false) { - assert_always(); - } - ~PosixMutant() = default; - bool Release() override { - assert_always(); - return false; - } + explicit PosixMutant(bool initial_owner) + : PosixConditionHandle(initial_owner) {} + ~PosixMutant() override = default; + bool Release() override { return handle_.Release(); } }; std::unique_ptr Mutant::Create(bool initial_owner) {