From cbc037e055c285573a11359d28e84f7b5bc050a8 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Mon, 13 Apr 2020 00:33:16 +0200 Subject: [PATCH] Add RTSemaphore and tests --- src/CMakeLists.txt | 1 + src/sfizz/RTSemaphore.cpp | 254 ++++++++++++++++++++++++++++++++++++++ src/sfizz/RTSemaphore.h | 50 ++++++++ tests/CMakeLists.txt | 1 + tests/SemaphoreT.cpp | 68 ++++++++++ 5 files changed, 374 insertions(+) create mode 100644 src/sfizz/RTSemaphore.cpp create mode 100644 src/sfizz/RTSemaphore.h create mode 100644 tests/SemaphoreT.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b3d472df..aceb1165 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,7 @@ set (SFIZZ_SOURCES sfizz/SfzFilter.cpp sfizz/Curve.cpp sfizz/Wavetables.cpp + sfizz/RTSemaphore.cpp sfizz/Effects.cpp sfizz/effects/Nothing.cpp sfizz/effects/Filter.cpp diff --git a/src/sfizz/RTSemaphore.cpp b/src/sfizz/RTSemaphore.cpp new file mode 100644 index 00000000..7a3340d5 --- /dev/null +++ b/src/sfizz/RTSemaphore.cpp @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "RTSemaphore.h" +#include +#include +#include + +RTSemaphore::RTSemaphore(unsigned value) +{ + std::error_code ec; + init(ec, value); + if (ec) + throw std::system_error(ec); + good_ = true; +} + +RTSemaphore::RTSemaphore(std::error_code &ec, unsigned value) noexcept +{ + init(ec, value); + good_ = ec ? false : true; +} + +RTSemaphore::~RTSemaphore() noexcept +{ + if (good_) { + std::error_code ec; + destroy(ec); + } +} + +void RTSemaphore::post() +{ + std::error_code ec; + post(ec); + if (ec) + throw std::system_error(ec); +} + +void RTSemaphore::wait() +{ + std::error_code ec; + wait(ec); + if (ec) + throw std::system_error(ec); +} + +bool RTSemaphore::try_wait() +{ + std::error_code ec; + bool b = try_wait(ec); + if (ec) + throw std::system_error(ec); + return b; +} + +#if defined(__APPLE__) +void RTSemaphore::init(std::error_code &ec, unsigned value) +{ + ec.clear(); + kern_return_t ret = semaphore_create(mach_task_self(), &sem_, SYNC_POLICY_FIFO, value); + if (ret != KERN_SUCCESS) + ec = std::error_code(ret, mach_category()); +} + +void RTSemaphore::destroy(std::error_code &ec) +{ + ec.clear(); + kern_return_t ret = semaphore_destroy(mach_task_self(), sem_); + if (ret != KERN_SUCCESS) + ec = std::error_code(ret, mach_category()); +} + +void RTSemaphore::post(std::error_code &ec) noexcept +{ + ec.clear(); + kern_return_t ret = semaphore_signal(sem_); + if (ret != KERN_SUCCESS) + ec = std::error_code(ret, mach_category()); +} + +void RTSemaphore::wait(std::error_code &ec) noexcept +{ + ec.clear(); + do { + kern_return_t ret = semaphore_wait(sem_); + switch (ret) { + case KERN_SUCCESS: + return; + case KERN_ABORTED: + break; + default: + ec = std::error_code(ret, mach_category()); + return; + } + } while (1); +} + +bool RTSemaphore::try_wait(std::error_code &ec) noexcept +{ + ec.clear(); + do { + const mach_timespec_t timeout = {0, 0}; + kern_return_t ret = semaphore_timedwait(sem_, timeout); + switch (ret) { + case KERN_SUCCESS: + return true; + case KERN_OPERATION_TIMED_OUT: + return false; + case KERN_ABORTED: + break; + default: + ec = std::error_code(ret, mach_category()); + return false; + } + } while (1); +} + +const std::error_category &RTSemaphore::mach_category() +{ + class mach_category : public std::error_category { + public: + const char *name() const noexcept override + { + return "kern_return_t"; + } + + std::string message(int condition) const override + { + const char *str = mach_error_string(condition); + return str ? str : ""; + } + }; + + static const mach_category cat; + return cat; +} +#elif defined(_WIN32) +void RTSemaphore::init(std::error_code &ec, unsigned value) +{ + ec.clear(); + sem_ = CreateSemaphore(nullptr, value, LONG_MAX, nullptr); + if (!sem_) + ec = std::error_code(GetLastError(), std::system_category()); +} + +void RTSemaphore::destroy(std::error_code &ec) +{ + ec.clear(); + if (CloseHandle(sem_) == 0) + ec = std::error_code(GetLastError(), std::system_category()); +} + +void RTSemaphore::post(std::error_code &ec) noexcept +{ + ec.clear(); + if (ReleaseSemaphore(sem_, 1, nullptr) == 0) + ec = std::error_code(GetLastError(), std::system_category()); +} + +void RTSemaphore::wait(std::error_code &ec) noexcept +{ + ec.clear(); + DWORD ret = WaitForSingleObject(sem_, INFINITE); + switch (ret) { + case WAIT_OBJECT_0: + return; + case WAIT_FAILED: + ec = std::error_code(GetLastError(), std::system_category()); + return; + default: + ec = std::error_code(ret, std::system_category()); + return; + } +} + +bool RTSemaphore::try_wait(std::error_code &ec) noexcept +{ + ec.clear(); + DWORD ret = WaitForSingleObject(sem_, 0); + switch (ret) { + case WAIT_OBJECT_0: + return true; + case WAIT_TIMEOUT: + return false; + case WAIT_FAILED: + ec = std::error_code(GetLastError(), std::system_category()); + return false; + default: + ec = std::error_code(ret, std::system_category()); + return false; + } +} +#else +void RTSemaphore::init(std::error_code &ec, unsigned value) +{ + ec.clear(); + if (sem_init(&sem_, 0, value) != 0) + ec = std::error_code(errno, std::generic_category()); +} + +void RTSemaphore::destroy(std::error_code &ec) +{ + ec.clear(); + if (sem_destroy(&sem_) != 0) + ec = std::error_code(errno, std::generic_category()); +} + +void RTSemaphore::post(std::error_code &ec) noexcept +{ + ec.clear(); + while (sem_post(&sem_) != 0) { + int e = errno; + if (e != EINTR) { + ec = std::error_code(e, std::generic_category()); + return; + } + } +} + +void RTSemaphore::wait(std::error_code &ec) noexcept +{ + ec.clear(); + while (sem_wait(&sem_) != 0) { + int e = errno; + if (e != EINTR) { + ec = std::error_code(e, std::generic_category()); + return; + } + } +} + +bool RTSemaphore::try_wait(std::error_code &ec) noexcept +{ + ec.clear(); + do { + if (sem_trywait(&sem_) == 0) + return true; + int e = errno; + switch (e) { + case EINTR: + break; + case EAGAIN: + return false; + default: + ec = std::error_code(e, std::generic_category()); + return false; + } + } while (1); +} +#endif diff --git a/src/sfizz/RTSemaphore.h b/src/sfizz/RTSemaphore.h new file mode 100644 index 00000000..4ce01112 --- /dev/null +++ b/src/sfizz/RTSemaphore.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#if defined(__APPLE__) +#include +#elif defined(_WIN32) +#include +#else +#include +#endif +#include + +class RTSemaphore { +public: + explicit RTSemaphore(unsigned value = 0); + explicit RTSemaphore(std::error_code &ec, unsigned value = 0) noexcept; + ~RTSemaphore() noexcept; + + RTSemaphore(const RTSemaphore &) = delete; + RTSemaphore &operator=(const RTSemaphore &) = delete; + + explicit operator bool() const noexcept { return good_; } + + void post(); + void wait(); + bool try_wait(); + + void post(std::error_code &ec) noexcept; + void wait(std::error_code &ec) noexcept; + bool try_wait(std::error_code &ec) noexcept; + +private: + void init(std::error_code &ec, unsigned value); + void destroy(std::error_code &ec); + +private: +#if defined(__APPLE__) + semaphore_t sem_ {}; + static const std::error_category &mach_category(); +#elif defined(_WIN32) + HANDLE sem_ {}; +#else + sem_t sem_ {}; +#endif + bool good_ {}; +}; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 423c867e..345a052b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -29,6 +29,7 @@ set(SFIZZ_TEST_SOURCES RegionTriggersT.cpp FloatHelpersT.cpp WavetablesT.cpp + SemaphoreT.cpp ) add_executable(sfizz_tests ${SFIZZ_TEST_SOURCES}) diff --git a/tests/SemaphoreT.cpp b/tests/SemaphoreT.cpp new file mode 100644 index 00000000..cc4b22c2 --- /dev/null +++ b/tests/SemaphoreT.cpp @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "sfizz/RTSemaphore.h" +#include "catch2/catch.hpp" +#include + +TEST_CASE("[Semaphore] Basic operations") +{ + RTSemaphore sem; + + REQUIRE(sem.try_wait() == false); + + sem.post(); + REQUIRE(sem.try_wait() == true); + REQUIRE(sem.try_wait() == false); + + sem.post(); + sem.post(); + REQUIRE(sem.try_wait() == true); + REQUIRE(sem.try_wait() == true); + REQUIRE(sem.try_wait() == false); + + sem.post(); + sem.post(); + sem.wait(); + sem.wait(); + + REQUIRE(sem.try_wait() == false); +} + +TEST_CASE("[Semaphore] Counter initialization") +{ + RTSemaphore sem(3); + + REQUIRE(sem.try_wait() == true); + REQUIRE(sem.try_wait() == true); + REQUIRE(sem.try_wait() == true); + REQUIRE(sem.try_wait() == false); +} + +TEST_CASE("[Semaphore] Thread synchronization") +{ + RTSemaphore sem1; + RTSemaphore sem2; + constexpr int n = 1000; + + std::thread t1([&]() { + for (int i = 0; i < n; ++i) { + sem1.post(); + sem2.wait(); + } + }); + std::thread t2([&]() { + for (int i = 0; i < n; ++i) { + sem2.post(); + sem1.wait(); + } + }); + + t1.join(); + t2.join(); + REQUIRE(sem1.try_wait() == false); + REQUIRE(sem2.try_wait() == false); +}