[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.
This commit is contained in:
parent
5d0efedaf4
commit
331bb0ea9a
|
@ -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<Mutant> 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<WaitResult, size_t> any_result;
|
||||
std::unique_ptr<Mutant> 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") {
|
||||
|
|
|
@ -317,6 +317,40 @@ class PosixCondition<Semaphore> : public PosixConditionBase {
|
|||
const uint32_t maximum_count_;
|
||||
};
|
||||
|
||||
template <>
|
||||
class PosixCondition<Mutant> : 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<std::mutex>(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 <typename T>
|
||||
class PosixThreadHandle : public T {
|
||||
|
@ -337,6 +371,7 @@ class PosixThreadHandle : public T {
|
|||
template <typename T>
|
||||
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<Semaphore>::PosixConditionHandle(uint32_t initial_count,
|
|||
: handle_(initial_count, maximum_count) {}
|
||||
|
||||
template <>
|
||||
PosixConditionHandle<Mutant>::PosixConditionHandle(bool manual_reset,
|
||||
bool initial_state)
|
||||
: handle_() {}
|
||||
PosixConditionHandle<Mutant>::PosixConditionHandle(bool initial_owner)
|
||||
: handle_(initial_owner) {}
|
||||
|
||||
template <>
|
||||
PosixConditionHandle<Timer>::PosixConditionHandle(bool manual_reset,
|
||||
|
@ -442,17 +476,12 @@ std::unique_ptr<Semaphore> Semaphore::Create(int initial_count,
|
|||
return std::make_unique<PosixSemaphore>(initial_count, maximum_count);
|
||||
}
|
||||
|
||||
// TODO(dougvj)
|
||||
class PosixMutant : public PosixConditionHandle<Mutant> {
|
||||
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> Mutant::Create(bool initial_owner) {
|
||||
|
|
Loading…
Reference in New Issue