From 7a080c917cc6128d46f3eca94413990cc007669d Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sun, 10 Oct 2021 18:41:14 +0100 Subject: Build with an STL semaphore when available --- libadhocutil/lockHelpers.h | 2 ++ libadhocutil/polyfill-semaphore.cpp | 43 ++++++++++++++++++++++++ libadhocutil/polyfill-semaphore.h | 52 +++++++++++++++++++++++++++++ libadhocutil/resourcePool.h | 22 +++++++++--- libadhocutil/resourcePool.impl.h | 34 +++++++++++++------ libadhocutil/semaphore.cpp | 44 ------------------------ libadhocutil/semaphore.h | 36 -------------------- libadhocutil/unittests/testCache.cpp | 2 -- libadhocutil/unittests/testResourcePool.cpp | 34 +++++++++++++++---- libadhocutil/unittests/testSemaphore.cpp | 50 +++++++++++++-------------- 10 files changed, 190 insertions(+), 129 deletions(-) create mode 100644 libadhocutil/polyfill-semaphore.cpp create mode 100644 libadhocutil/polyfill-semaphore.h delete mode 100644 libadhocutil/semaphore.cpp delete mode 100644 libadhocutil/semaphore.h diff --git a/libadhocutil/lockHelpers.h b/libadhocutil/lockHelpers.h index d306f9c..3b3bda8 100644 --- a/libadhocutil/lockHelpers.h +++ b/libadhocutil/lockHelpers.h @@ -1,6 +1,8 @@ #ifndef ADHOCUTIL_LOCKHELPERS_H #define ADHOCUTIL_LOCKHELPERS_H +#include + #define LIBADHOC_LOCK_CONCAT2(a, b) a##b #define LIBADHOC_LOCK_CONCAT(a, b) LIBADHOC_LOCK_CONCAT2(a, b) #define LIBADHOC_LOCK_WITHLINE(a) LIBADHOC_LOCK_CONCAT(a, __LINE__) diff --git a/libadhocutil/polyfill-semaphore.cpp b/libadhocutil/polyfill-semaphore.cpp new file mode 100644 index 0000000..efe5be6 --- /dev/null +++ b/libadhocutil/polyfill-semaphore.cpp @@ -0,0 +1,43 @@ +#include "polyfill-semaphore.h" +#include + +namespace AdHoc { + Semaphore::Semaphore(std::ptrdiff_t initial) : count(initial) { } + + void + Semaphore::release() + { + std::scoped_lock lock(mutex); + ++count; + condition.notify_one(); + } + + void + Semaphore::acquire() + { + std::unique_lock lock(mutex); + while (!count) { + condition.wait(lock); + } + --count; + } + + bool + Semaphore::try_acquire_for(std::chrono::milliseconds timeout) + { + std::unique_lock lock(mutex); + while (!count) { + if (condition.wait_for(lock, timeout) == std::cv_status::timeout) { + return false; + } + } + --count; + return true; + } + + std::ptrdiff_t + Semaphore::freeCount() const + { + return count; + } +} diff --git a/libadhocutil/polyfill-semaphore.h b/libadhocutil/polyfill-semaphore.h new file mode 100644 index 0000000..273d0ec --- /dev/null +++ b/libadhocutil/polyfill-semaphore.h @@ -0,0 +1,52 @@ +#ifndef ADHOCUTIL_SEMAPHORE_H +#define ADHOCUTIL_SEMAPHORE_H + +// Borrowed from StackOverflow +// http://stackoverflow.com/questions/4792449/c0x-has-no-semaphores-how-to-synchronize-threads + +#include "visibility.h" +#include +#include +#include + +namespace AdHoc { + /// A portable semaphore with timeout support + class DLL_PUBLIC Semaphore { + public: + /// Construct a new semaphore with optional initial count. + explicit Semaphore(std::ptrdiff_t initial); + + /// Notify one waiting thread. + void release(); + /// Wait for a single count. + void acquire(); + /// Wait for a single count with timeout. + /// @param ms Timeout how long to wait. + bool try_acquire_for(std::chrono::milliseconds ms); + /// Wait for a single count with timeout. + /// @param ms Timeout how long to wait. + bool + try_acquire_for(unsigned int ms) + { + return try_acquire_for(std::chrono::milliseconds(ms)); + } + /// Wait for a single count with timeout. + /// @param ms Timeout how long to wait. + template + bool + try_acquire_for(const std::chrono::duration & rel_time) + { + return std::chrono::duration_cast(rel_time); + } + + /// Free + [[nodiscard]] std::ptrdiff_t freeCount() const; + + private: + std::mutex mutex; + std::condition_variable condition; + std::ptrdiff_t count; + }; +} + +#endif diff --git a/libadhocutil/resourcePool.h b/libadhocutil/resourcePool.h index c7fed42..2fcaf6b 100644 --- a/libadhocutil/resourcePool.h +++ b/libadhocutil/resourcePool.h @@ -3,7 +3,11 @@ #include "c++11Helpers.h" #include "exception.h" -#include "semaphore.h" +#ifdef __cpp_lib_semaphore +# include +#else +# include "polyfill-semaphore.h" +#endif #include "visibility.h" #include #include @@ -15,6 +19,11 @@ #include namespace AdHoc { +#ifdef __cpp_lib_semaphore + using SemaphoreType = std::counting_semaphore<>; +#else + using SemaphoreType = Semaphore; +#endif template class ResourcePool; /// A handle to a resource allocated from a ResourcePool. @@ -59,7 +68,7 @@ namespace AdHoc { /// Create a new resource pool. /// @param maxSize The upper limit of how many concurrent active resources there can be. /// @param keep The number of resources to cache for reuse. - ResourcePool(std::size_t maxSize, std::size_t keep); + ResourcePool(std::ptrdiff_t maxSize, std::size_t keep); virtual ~ResourcePool(); /// Standard move/copy support @@ -69,6 +78,9 @@ namespace AdHoc { ResourceHandle get(); /// Get a resource from the pool (with timeout on max size of pool) /// @param ms Timeout in milliseconds. + ResourceHandle get(const std::chrono::milliseconds ms); + /// Get a resource from the pool (with timeout on max size of pool) + /// @param ms Timeout in milliseconds. ResourceHandle get(unsigned int ms); /// Get a new handle to the resource previous allocated to the current. ResourceHandle getMine(); @@ -79,8 +91,10 @@ namespace AdHoc { std::size_t inUseCount() const; /// Get number of available cached resources. std::size_t availableCount() const; +#ifndef __cpp_lib_semaphore /// Get number of free slots. - std::size_t freeCount() const; + std::ptrdiff_t freeCount() const; +#endif protected: /// Create a new resource instance to add to the pool. @@ -101,7 +115,7 @@ namespace AdHoc { DLL_PRIVATE ResourceHandle getOne(); mutable std::shared_mutex lock; - Semaphore poolSize; + SemaphoreType poolSize; std::size_t keep; Available available; InUse inUse; diff --git a/libadhocutil/resourcePool.impl.h b/libadhocutil/resourcePool.impl.h index eb34b22..5e89709 100644 --- a/libadhocutil/resourcePool.impl.h +++ b/libadhocutil/resourcePool.impl.h @@ -4,7 +4,11 @@ #include "lockHelpers.h" #include "resourcePool.h" // IWYU pragma: export #include "safeMapFind.h" -#include "semaphore.h" +#ifdef __cpp_lib_semaphore +# include +#else +# include "polyfill-semaphore.h" +#endif #include #include #include @@ -114,7 +118,7 @@ namespace AdHoc { // ResourcePool // - template ResourcePool::ResourcePool(std::size_t max, std::size_t k) : poolSize(max), keep(k) { } + template ResourcePool::ResourcePool(std::ptrdiff_t max, std::size_t k) : poolSize(max), keep(k) { } template ResourcePool::~ResourcePool() { @@ -151,13 +155,15 @@ namespace AdHoc { return available.size(); } +#ifndef __cpp_lib_semaphore template - std::size_t + std::ptrdiff_t ResourcePool::freeCount() const { SharedLock(lock); return poolSize.freeCount(); } +#endif template ResourceHandle @@ -179,32 +185,39 @@ namespace AdHoc { ResourceHandle ResourcePool::get() { - poolSize.wait(); + poolSize.acquire(); try { return ResourceHandle(getOne()); } catch (...) { - poolSize.notify(); + poolSize.release(); throw; } } template ResourceHandle - ResourcePool::get(unsigned int timeout) + ResourcePool::get(const std::chrono::milliseconds timeout) { - if (!poolSize.wait(timeout)) { + if (!poolSize.try_acquire_for(timeout)) { throw TimeOutOnResourcePoolT(); } try { return getOne(); } catch (...) { - poolSize.notify(); + poolSize.release(); throw; } } + template + ResourceHandle + ResourcePool::get(unsigned int ms) + { + return get(std::chrono::milliseconds(ms)); + } + template ResourceHandle ResourcePool::getOne() @@ -242,7 +255,7 @@ namespace AdHoc { } catch (...) { } - poolSize.notify(); + poolSize.release(); } template @@ -251,7 +264,7 @@ namespace AdHoc { { Lock(lock); removeFrom(r, inUse); - poolSize.notify(); + poolSize.release(); } template @@ -275,7 +288,6 @@ namespace AdHoc { NoCurrentResourceT::NoCurrentResourceT(const std::thread::id & id) : NoCurrentResource(id, typeid(R).name()) { } - } #endif diff --git a/libadhocutil/semaphore.cpp b/libadhocutil/semaphore.cpp deleted file mode 100644 index e6ec7d0..0000000 --- a/libadhocutil/semaphore.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "semaphore.h" -#include - -namespace AdHoc { - Semaphore::Semaphore(std::size_t initial) : count(initial) { } - - void - Semaphore::notify() - { - std::scoped_lock lock(mutex); - ++count; - condition.notify_one(); - } - - void - Semaphore::wait() - { - std::unique_lock lock(mutex); - while (!count) { - condition.wait(lock); - } - --count; - } - - bool - Semaphore::wait(unsigned int timeout) - { - const auto expiry = std::chrono::milliseconds(timeout); - std::unique_lock lock(mutex); - while (!count) { - if (condition.wait_for(lock, expiry) == std::cv_status::timeout) { - return false; - } - } - --count; - return true; - } - - std::size_t - Semaphore::freeCount() const - { - return count; - } -} diff --git a/libadhocutil/semaphore.h b/libadhocutil/semaphore.h deleted file mode 100644 index 8b03907..0000000 --- a/libadhocutil/semaphore.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef ADHOCUTIL_SEMAPHORE_H -#define ADHOCUTIL_SEMAPHORE_H - -// Borrowed from StackOverflow -// http://stackoverflow.com/questions/4792449/c0x-has-no-semaphores-how-to-synchronize-threads - -#include "visibility.h" -#include -#include -#include - -namespace AdHoc { - /// A portable semaphore with timeout support - class DLL_PUBLIC Semaphore { - public: - /// Construct a new semaphore with optional initial count. - explicit Semaphore(std::size_t initial = 0); - - /// Notify one waiting thread. - void notify(); - /// Wait for a single count. - void wait(); - /// Wait for a single count with timeout. - /// @param ms Timeout in milliseconds. - bool wait(unsigned int ms); - /// Free - [[nodiscard]] std::size_t freeCount() const; - - private: - std::mutex mutex; - std::condition_variable condition; - std::size_t count; - }; -} - -#endif diff --git a/libadhocutil/unittests/testCache.cpp b/libadhocutil/unittests/testCache.cpp index c6ab6b4..0cdd0ea 100644 --- a/libadhocutil/unittests/testCache.cpp +++ b/libadhocutil/unittests/testCache.cpp @@ -8,12 +8,10 @@ #include #include -// NOLINTNEXTLINE(hicpp-special-member-functions) class Obj { public: // cppcheck-suppress noExplicitConstructor; NOLINTNEXTLINE(hicpp-explicit-conversions) Obj(int i) : v(i) { } - void operator=(const Obj &) = delete; bool operator==(const int & i) const { diff --git a/libadhocutil/unittests/testResourcePool.cpp b/libadhocutil/unittests/testResourcePool.cpp index baa965a..a5f06a2 100644 --- a/libadhocutil/unittests/testResourcePool.cpp +++ b/libadhocutil/unittests/testResourcePool.cpp @@ -4,7 +4,11 @@ #include "c++11Helpers.h" #include "lockHelpers.h" #include "resourcePool.impl.h" -#include "semaphore.h" +#ifdef __cpp_lib_semaphore +# include +#else +# include "polyfill-semaphore.h" +#endif #include #include #include @@ -330,21 +334,21 @@ BOOST_AUTO_TEST_CASE(threading1, *boost::unit_test::timeout(10)) } static void -acquireAndKeepFor1Second(TRPSmall * pool, AdHoc::Semaphore & s) +acquireAndKeepFor1Second(TRPSmall * pool, AdHoc::SemaphoreType & s) { auto r = pool->get(); static std::mutex m; ScopeLock(m) { BOOST_CHECK(r); } - s.notify(); + s.release(); sleep(1); } BOOST_AUTO_TEST_CASE(threading2) { TRPSmall pool; - AdHoc::Semaphore s; + AdHoc::SemaphoreType s {0}; std::thread t1([&pool, &s]() { acquireAndKeepFor1Second(&pool, s); }); @@ -355,9 +359,9 @@ BOOST_AUTO_TEST_CASE(threading2) acquireAndKeepFor1Second(&pool, s); }); - s.wait(); - s.wait(); - s.wait(); + s.acquire(); + s.acquire(); + s.acquire(); BOOST_REQUIRE_THROW(pool.get(100), AdHoc::TimeOutOnResourcePoolT); BOOST_REQUIRE_EQUAL(3, pool.inUseCount()); @@ -416,20 +420,32 @@ BOOST_AUTO_TEST_CASE(createFail) BOOST_REQUIRE_EQUAL(0, MockResource::count); BOOST_REQUIRE_EQUAL(0, pool.availableCount()); BOOST_REQUIRE_EQUAL(0, pool.inUseCount()); +#ifndef __cpp_lib_semaphore BOOST_REQUIRE_EQUAL(3, pool.freeCount()); +#endif BOOST_REQUIRE_THROW(pool.get(), std::exception); +#ifndef __cpp_lib_semaphore BOOST_REQUIRE_EQUAL(3, pool.freeCount()); +#endif BOOST_REQUIRE_THROW(pool.get(0), std::exception); +#ifndef __cpp_lib_semaphore BOOST_REQUIRE_EQUAL(3, pool.freeCount()); +#endif BOOST_REQUIRE_THROW(pool.get(100), std::exception); +#ifndef __cpp_lib_semaphore BOOST_REQUIRE_EQUAL(3, pool.freeCount()); +#endif BOOST_REQUIRE_EQUAL(0, MockResource::count); BOOST_REQUIRE_EQUAL(0, pool.availableCount()); BOOST_REQUIRE_EQUAL(0, pool.inUseCount()); BOOST_REQUIRE_THROW(pool.get(), std::exception); +#ifndef __cpp_lib_semaphore BOOST_REQUIRE_EQUAL(3, pool.freeCount()); +#endif BOOST_REQUIRE_THROW(pool.get(), std::exception); +#ifndef __cpp_lib_semaphore BOOST_REQUIRE_EQUAL(3, pool.freeCount()); +#endif } BOOST_AUTO_TEST_CASE(returnFail) @@ -440,11 +456,15 @@ BOOST_AUTO_TEST_CASE(returnFail) BOOST_CHECK(rh); BOOST_REQUIRE_EQUAL(0, pool.availableCount()); BOOST_REQUIRE_EQUAL(1, pool.inUseCount()); +#ifndef __cpp_lib_semaphore BOOST_REQUIRE_EQUAL(2, pool.freeCount()); +#endif } BOOST_REQUIRE_EQUAL(0, pool.availableCount()); BOOST_REQUIRE_EQUAL(0, pool.inUseCount()); +#ifndef __cpp_lib_semaphore BOOST_REQUIRE_EQUAL(3, pool.freeCount()); +#endif } BOOST_AUTO_TEST_CASE(exception_msgs) diff --git a/libadhocutil/unittests/testSemaphore.cpp b/libadhocutil/unittests/testSemaphore.cpp index 0de899e..999cff3 100644 --- a/libadhocutil/unittests/testSemaphore.cpp +++ b/libadhocutil/unittests/testSemaphore.cpp @@ -1,66 +1,66 @@ #define BOOST_TEST_MODULE Semaphore #include -#include +#include #include #include BOOST_AUTO_TEST_CASE(addRemoveOne) { - AdHoc::Semaphore s; - s.notify(); - s.wait(); + AdHoc::Semaphore s {0}; + s.release(); + s.acquire(); } BOOST_AUTO_TEST_CASE(initial) { AdHoc::Semaphore s(2); - s.wait(); - s.wait(); + s.acquire(); + s.acquire(); // cppcheck-suppress assertWithSideEffect - BOOST_REQUIRE_EQUAL(false, s.wait(0)); + BOOST_REQUIRE_EQUAL(false, s.try_acquire_for(0)); } BOOST_AUTO_TEST_CASE(addRemoveSome) { - AdHoc::Semaphore s; + AdHoc::Semaphore s {0}; BOOST_REQUIRE_EQUAL(0, s.freeCount()); - s.notify(); + s.release(); BOOST_REQUIRE_EQUAL(1, s.freeCount()); - s.notify(); + s.release(); BOOST_REQUIRE_EQUAL(2, s.freeCount()); - s.notify(); + s.release(); BOOST_REQUIRE_EQUAL(3, s.freeCount()); - s.wait(); + s.acquire(); BOOST_REQUIRE_EQUAL(2, s.freeCount()); - s.wait(); + s.acquire(); BOOST_REQUIRE_EQUAL(1, s.freeCount()); - s.wait(); + s.acquire(); BOOST_REQUIRE_EQUAL(0, s.freeCount()); } BOOST_AUTO_TEST_CASE(addRemoveTimeOut) { - AdHoc::Semaphore s; - s.notify(); - s.wait(); + AdHoc::Semaphore s {0}; + s.release(); + s.acquire(); // cppcheck-suppress assertWithSideEffect - BOOST_REQUIRE_EQUAL(false, s.wait(100)); + BOOST_REQUIRE_EQUAL(false, s.try_acquire_for(100)); // cppcheck-suppress assertWithSideEffect - BOOST_REQUIRE_EQUAL(false, s.wait(0)); + BOOST_REQUIRE_EQUAL(false, s.try_acquire_for(0)); } BOOST_AUTO_TEST_CASE(addRemoveWait) { - AdHoc::Semaphore s; - s.notify(); - s.wait(); + AdHoc::Semaphore s {0}; + s.release(); + s.acquire(); std::thread th([&s]() { usleep(100000); - s.notify(); + s.release(); }); // cppcheck-suppress assertWithSideEffect - BOOST_REQUIRE_EQUAL(false, s.wait(1)); - s.wait(); + BOOST_REQUIRE_EQUAL(false, s.try_acquire_for(1)); + s.acquire(); th.join(); } -- cgit v1.2.3