[threading linux] Implement Semaphore
Test acquiring and releasing semaphores on same and on different threads. Test previous_count values. Test WaitAll and WaitAny. Add tests for invalid semaphore creation parameters but disactivated as they do not pass on any platform. These should be enabled and the implementations fixed to match documentation.
This commit is contained in:
parent
75d54e2fa2
commit
5d0efedaf4
|
@ -280,8 +280,156 @@ TEST_CASE("Wait on Multiple Events", "Event") {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Wait on Semaphore", "Semaphore") {
|
TEST_CASE("Wait on Semaphore", "Semaphore") {
|
||||||
// TODO(bwrsandman):
|
WaitResult result;
|
||||||
REQUIRE(true);
|
std::unique_ptr<Semaphore> 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, 5>{
|
||||||
|
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<WaitResult, size_t> any_result;
|
||||||
|
int previous_count;
|
||||||
|
std::unique_ptr<Semaphore> 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") {
|
TEST_CASE("Wait on Mutant", "Mutant") {
|
||||||
|
|
|
@ -290,6 +290,33 @@ class PosixCondition<Event> : public PosixConditionBase {
|
||||||
const bool manual_reset_;
|
const bool manual_reset_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
class PosixCondition<Semaphore> : 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<std::mutex>(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
|
// Native posix thread handle
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class PosixThreadHandle : public T {
|
class PosixThreadHandle : public T {
|
||||||
|
@ -311,6 +338,7 @@ template <typename T>
|
||||||
class PosixConditionHandle : public T {
|
class PosixConditionHandle : public T {
|
||||||
public:
|
public:
|
||||||
PosixConditionHandle(bool manual_reset, bool initial_state);
|
PosixConditionHandle(bool manual_reset, bool initial_state);
|
||||||
|
PosixConditionHandle(uint32_t initial_count, uint32_t maximum_count);
|
||||||
~PosixConditionHandle() override = default;
|
~PosixConditionHandle() override = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -322,9 +350,9 @@ class PosixConditionHandle : public T {
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
PosixConditionHandle<Semaphore>::PosixConditionHandle(bool manual_reset,
|
PosixConditionHandle<Semaphore>::PosixConditionHandle(uint32_t initial_count,
|
||||||
bool initial_state)
|
uint32_t maximum_count)
|
||||||
: handle_() {}
|
: handle_(initial_count, maximum_count) {}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
PosixConditionHandle<Mutant>::PosixConditionHandle(bool manual_reset,
|
PosixConditionHandle<Mutant>::PosixConditionHandle(bool manual_reset,
|
||||||
|
@ -394,17 +422,18 @@ std::unique_ptr<Event> Event::CreateAutoResetEvent(bool initial_state) {
|
||||||
return std::make_unique<PosixEvent>(false, initial_state);
|
return std::make_unique<PosixEvent>(false, initial_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(dougvj)
|
|
||||||
class PosixSemaphore : public PosixConditionHandle<Semaphore> {
|
class PosixSemaphore : public PosixConditionHandle<Semaphore> {
|
||||||
public:
|
public:
|
||||||
PosixSemaphore(int initial_count, int maximum_count)
|
PosixSemaphore(int initial_count, int maximum_count)
|
||||||
: PosixConditionHandle(false, false) {
|
: PosixConditionHandle(static_cast<uint32_t>(initial_count),
|
||||||
assert_always();
|
static_cast<uint32_t>(maximum_count)) {}
|
||||||
}
|
|
||||||
~PosixSemaphore() override = default;
|
~PosixSemaphore() override = default;
|
||||||
bool Release(int release_count, int* out_previous_count) override {
|
bool Release(int release_count, int* out_previous_count) override {
|
||||||
assert_always();
|
if (release_count < 1) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
return handle_.Release(static_cast<uint32_t>(release_count),
|
||||||
|
out_previous_count);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue