From d49d62723c3ca5046482c1b6e25b14ce85f4fbd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Fr=C3=B6jdh?= Date: Thu, 1 Oct 2026 11:19:29 +0200 Subject: [PATCH] added missing files --- .../include/sls/counting_semaphore.h | 80 ++++++++++ .../tests/test-counting_semaphore.cpp | 147 ++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 slsSupportLib/include/sls/counting_semaphore.h create mode 100644 slsSupportLib/tests/test-counting_semaphore.cpp diff --git a/slsSupportLib/include/sls/counting_semaphore.h b/slsSupportLib/include/sls/counting_semaphore.h new file mode 100644 index 000000000..2f631e06d --- /dev/null +++ b/slsSupportLib/include/sls/counting_semaphore.h @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: LGPL-3.0-or-other +// Copyright (C) 2021 Contributors to the SLS Detector Package +#pragma once + +/** + * @file counting_semaphore.h + * @short C++17 backport of std::counting_semaphore and std::binary_semaphore + * + * Mirrors the C++20 types so that the project can switch by + * replacing this include with and sls:: with std::. Unnamed POSIX + * semaphores (sem_init) cannot be used instead as they are not implemented on + * macOS. + * + * Differences to the std types: + * - try_acquire_for and try_acquire_until are not implemented + * - the constructor is not constexpr + * - try_acquire never fails spuriously (std allows it to) + * - the preconditions (0 <= desired <= max() and + * 0 <= update <= max() - counter) are not checked, as in std violating + * them is undefined behaviour + * + * Built on std::mutex and std::condition_variable, therefore NOT + * async-signal-safe. Do not call release() from a signal handler. + */ + +#include +#include +#include +#include + +namespace sls { + +template ::max()> +class counting_semaphore { + static_assert(LeastMaxValue >= 0, "LeastMaxValue must be non-negative"); + + public: + static constexpr std::ptrdiff_t max() noexcept { return LeastMaxValue; } + + explicit counting_semaphore(std::ptrdiff_t desired) : count_(desired) {} + + counting_semaphore(const counting_semaphore &) = delete; + counting_semaphore &operator=(const counting_semaphore &) = delete; + + void acquire() { + std::unique_lock lk(mtx_); + cv_.wait(lk, [this] { return count_ > 0; }); + --count_; + } + + bool try_acquire() noexcept { + std::lock_guard lk(mtx_); + if (count_ == 0) + return false; + --count_; + return true; + } + + void release(std::ptrdiff_t update = 1) { + // notify while holding the lock: a thread returning from acquire() + // may destroy the semaphore, which must not happen before we are done + // with the condition variable + std::lock_guard lk(mtx_); + count_ += update; + if (update == 1) + cv_.notify_one(); + else + cv_.notify_all(); + } + + private: + std::mutex mtx_; + std::condition_variable cv_; + std::ptrdiff_t count_; +}; + +using binary_semaphore = counting_semaphore<1>; + +} // namespace sls diff --git a/slsSupportLib/tests/test-counting_semaphore.cpp b/slsSupportLib/tests/test-counting_semaphore.cpp new file mode 100644 index 000000000..6b36a17a6 --- /dev/null +++ b/slsSupportLib/tests/test-counting_semaphore.cpp @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: LGPL-3.0-or-other +// Copyright (C) 2021 Contributors to the SLS Detector Package +#include "catch.hpp" +#include "sls/counting_semaphore.h" + +#include +#include +#include +#include +#include +#include + +namespace sls { + +// Only what std::counting_semaphore guarantees is tested, so that the tests +// also pass with the std types once the project moves to C++20. + +static_assert(!std::is_copy_constructible>::value, + "semaphore must not be copyable"); +static_assert(!std::is_copy_assignable>::value, + "semaphore must not be copyable"); +static_assert(!std::is_move_constructible>::value, + "semaphore must not be movable"); +static_assert( + std::is_same>::value, + "binary_semaphore is a counting_semaphore with a least max value of 1"); + +TEST_CASE("max is at least the least max value", "[support][semaphore]") { + CHECK(binary_semaphore::max() >= 1); + CHECK(counting_semaphore<5>::max() >= 5); + CHECK(counting_semaphore<>::max() >= 1); +} + +TEST_CASE("try_acquire takes what the semaphore was constructed with", + "[support][semaphore]") { + counting_semaphore<> s(2); + CHECK(s.try_acquire()); + CHECK(s.try_acquire()); + CHECK_FALSE(s.try_acquire()); +} + +TEST_CASE("try_acquire fails on an empty semaphore", "[support][semaphore]") { + binary_semaphore s{0}; + CHECK_FALSE(s.try_acquire()); + s.release(); + CHECK(s.try_acquire()); + CHECK_FALSE(s.try_acquire()); +} + +TEST_CASE("release with an update count adds that many", + "[support][semaphore]") { + counting_semaphore<> s(0); + s.release(3); + s.release(); + for (int i = 0; i != 4; ++i) { + CHECK(s.try_acquire()); + } + CHECK_FALSE(s.try_acquire()); +} + +TEST_CASE("acquire does not block when the count is positive", + "[support][semaphore]") { + counting_semaphore<> s(1); + s.acquire(); + CHECK_FALSE(s.try_acquire()); +} + +TEST_CASE("acquire blocks until release", "[support][semaphore]") { + binary_semaphore s{0}; + std::atomic acquired{false}; + std::thread t([&] { + s.acquire(); + acquired = true; + }); + + // cannot have passed acquire, no matter how the thread is scheduled + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + CHECK_FALSE(acquired); + + s.release(); + t.join(); + CHECK(acquired); + CHECK_FALSE(s.try_acquire()); +} + +TEST_CASE("release with an update count wakes up that many waiting threads", + "[support][semaphore]") { + const int nThreads = 4; + counting_semaphore<> s(0); + std::atomic acquired{0}; + std::vector threads; + for (int i = 0; i != nThreads; ++i) { + threads.emplace_back([&] { + s.acquire(); + ++acquired; + }); + } + s.release(nThreads); + for (auto &t : threads) { + t.join(); + } + CHECK(acquired == nThreads); + CHECK_FALSE(s.try_acquire()); +} + +TEST_CASE("every release is acquired exactly once", "[support][semaphore]") { + const int nProducers = 3; + const int nConsumers = 3; + const int nPerThread = 2000; + counting_semaphore<> s(0); + std::atomic acquired{0}; + std::vector threads; + for (int i = 0; i != nConsumers; ++i) { + threads.emplace_back([&] { + for (int j = 0; j != nPerThread; ++j) { + s.acquire(); + ++acquired; + } + }); + } + for (int i = 0; i != nProducers; ++i) { + threads.emplace_back([&] { + for (int j = 0; j != nPerThread; ++j) { + s.release(); + } + }); + } + for (auto &t : threads) { + t.join(); + } + CHECK(acquired == nConsumers * nPerThread); + CHECK_FALSE(s.try_acquire()); +} + +TEST_CASE("a waiter can destroy the semaphore as soon as it has acquired", + "[support][semaphore]") { + for (int i = 0; i != 200; ++i) { + auto *done = new binary_semaphore(0); + std::thread worker([done] { done->release(); }); + done->acquire(); + delete done; + worker.join(); + } + SUCCEED(); +} + +} // namespace sls