[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:
Sandy Carter 2018-12-09 12:51:11 -08:00 committed by Rick Gibbed
parent 75d54e2fa2
commit 5d0efedaf4
2 changed files with 188 additions and 11 deletions

View File

@ -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<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") {

View File

@ -290,6 +290,33 @@ class PosixCondition<Event> : public PosixConditionBase {
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
template <typename T>
class PosixThreadHandle : public T {
@ -311,6 +338,7 @@ template <typename T>
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<Semaphore>::PosixConditionHandle(bool manual_reset,
bool initial_state)
: handle_() {}
PosixConditionHandle<Semaphore>::PosixConditionHandle(uint32_t initial_count,
uint32_t maximum_count)
: handle_(initial_count, maximum_count) {}
template <>
PosixConditionHandle<Mutant>::PosixConditionHandle(bool manual_reset,
@ -394,18 +422,19 @@ std::unique_ptr<Event> Event::CreateAutoResetEvent(bool initial_state) {
return std::make_unique<PosixEvent>(false, initial_state);
}
// TODO(dougvj)
class PosixSemaphore : public PosixConditionHandle<Semaphore> {
public:
PosixSemaphore(int initial_count, int maximum_count)
: PosixConditionHandle(false, false) {
assert_always();
}
: PosixConditionHandle(static_cast<uint32_t>(initial_count),
static_cast<uint32_t>(maximum_count)) {}
~PosixSemaphore() override = default;
bool Release(int release_count, int* out_previous_count) override {
assert_always();
if (release_count < 1) {
return false;
}
return handle_.Release(static_cast<uint32_t>(release_count),
out_previous_count);
}
};
std::unique_ptr<Semaphore> Semaphore::Create(int initial_count,