diff --git a/docs/utilities/semaphore.md b/docs/utilities/semaphore.md new file mode 100644 index 00000000..8a33f308 --- /dev/null +++ b/docs/utilities/semaphore.md @@ -0,0 +1,31 @@ +--- +title: "semaphore" +--- + +{{< callout type="info">}} + Header: `semaphore.h` + Since: TBC + Similar to: [std::counting_semaphore, std::binary_semaphore](https://en.cppreference.com/cpp/thread/counting_semaphore) +{{< /callout >}} + +This header attempts to replicate standard counting and binary semaphores. + +## Supported implementations + +| Implementation | Associated definitions | +|-------------------|---------------------------------------------------------------| +| FreeRTOS | `ETL_TARGET_OS_FREERTOS` | +| STL/STD (C++20) | `ETL_USING_STL && ETL_USING_CPP20 && ETL_USING_STD_SEMAPHORE` | +| GCC/clang atomics | `(ETL_COMPILER_GCC \|\| ETL_COMPILER_CLANG) && ETL_USING_CPP11` | + + +## Implementation quirks and deviations from standard + +### FreeRTOS + +On FreeRTOS, `LeastMaxValue` must be `1` or greater and not `0` or greater as the standard specifies. + +### GCC/clang sync + +The GCC/clang sync implementation uses `etl::chrono::steady_clock`. Make sure to +provide the methods associated with this clock: see [the relevant documentation](/docs/chrono/clocks/#platform-clock-access). diff --git a/include/etl/file_error_numbers.h b/include/etl/file_error_numbers.h index 76175a77..1d4a2901 100644 --- a/include/etl/file_error_numbers.h +++ b/include/etl/file_error_numbers.h @@ -114,4 +114,5 @@ SOFTWARE. #define ETL_INTRUSIVE_AVL_TREE_FILE_ID "81" #define ETL_FUTURE_FILE_ID "82" #define ETL_CYCLIC_VALUE_FILE_ID "83" +#define ETL_SEMAPHORE_FILE_ID "84" #endif diff --git a/include/etl/platform.h b/include/etl/platform.h index c59b7681..85002890 100644 --- a/include/etl/platform.h +++ b/include/etl/platform.h @@ -110,6 +110,15 @@ SOFTWARE. #endif #define ETL_NOT_USING_STD_MUTEX (1 - ETL_USING_STD_MUTEX) +//************************************* +// Helper macros for ETL_NO_STD_SEMAPHORE. +#if defined(ETL_NO_STD_SEMAPHORE) + #define ETL_USING_STD_SEMAPHORE 0 +#else + #define ETL_USING_STD_SEMAPHORE 1 +#endif +#define ETL_NOT_USING_STD_SEMAPHORE (1 - ETL_USING_STD_SEMAPHORE) + //************************************* // Helper macros for ETL_STLPORT. #if defined(ETL_STLPORT) diff --git a/include/etl/semaphore.h b/include/etl/semaphore.h new file mode 100644 index 00000000..1b2bb8d2 --- /dev/null +++ b/include/etl/semaphore.h @@ -0,0 +1,60 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Julien Hecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_SEMAPHORE_INCLUDED +#define ETL_SEMAPHORE_INCLUDED + +#include "platform.h" + +#include "semaphore/semaphore_exceptions.h" + +#if defined(ETL_TARGET_OS_FREERTOS) + #include "semaphore/semaphore_freertos.h" // IWYU pragma: export + #define ETL_HAS_SEMAPHORE 1 +#elif ETL_USING_STL && ETL_USING_CPP20 && ETL_USING_STD_SEMAPHORE + #include "semaphore/semaphore_std.h" // IWYU pragma: export + #define ETL_HAS_SEMAPHORE 1 +#elif defined(ETL_COMPILER_GCC) && ETL_USING_CPP11 + #include "semaphore/semaphore_gcc_sync.h" // IWYU pragma: export + #define ETL_HAS_SEMAPHORE 1 +#elif defined(ETL_COMPILER_CLANG) && ETL_USING_CPP11 + #include "semaphore/semaphore_clang_sync.h" // IWYU pragma: export + #define ETL_HAS_SEMAPHORE 1 +#else + #define ETL_HAS_SEMAPHORE 0 +#endif + +namespace etl +{ + namespace traits + { + static ETL_CONSTANT bool has_semaphore = (ETL_HAS_SEMAPHORE == 1); + } +} // namespace etl + +#endif diff --git a/include/etl/semaphore/semaphore_clang_sync.h b/include/etl/semaphore/semaphore_clang_sync.h new file mode 100644 index 00000000..0c7e88d0 --- /dev/null +++ b/include/etl/semaphore/semaphore_clang_sync.h @@ -0,0 +1,34 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Julien Hecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_SEMAPHORE_CLANG_SYNC_INCLUDED +#define ETL_SEMAPHORE_CLANG_SYNC_INCLUDED + +#include "semaphore_gcc_sync.h" + +#endif diff --git a/include/etl/semaphore/semaphore_exceptions.h b/include/etl/semaphore/semaphore_exceptions.h new file mode 100644 index 00000000..23b7b007 --- /dev/null +++ b/include/etl/semaphore/semaphore_exceptions.h @@ -0,0 +1,71 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Julien Hecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_SEMAPHORE_EXCEPTIONS_INCLUDED +#define ETL_SEMAPHORE_EXCEPTIONS_INCLUDED + +#include "../platform.h" +#include "../error_handler.h" +#include "../exception.h" + +namespace etl +{ + + //*************************************************************************** + ///\ingroup semaphore + /// Exception base for semaphores + //*************************************************************************** + class semaphore_exception : public exception + { + public: + + semaphore_exception(string_type reason_, string_type file_name_, numeric_type line_number_) + : exception(reason_, file_name_, line_number_) + { + } + }; + + //*************************************************************************** + ///\ingroup semaphore + /// Exception thrown when the semaphore value gets out of bounds + /// For example: + /// - Creating a semaphore with a `desired` value over `max()` + /// - Releasing a semaphore with a negative update value, or value that would bring it over `max()` + //*************************************************************************** + class semaphore_out_of_bounds : public semaphore_exception + { + public: + + semaphore_out_of_bounds(string_type file_name_, numeric_type line_number_) + : semaphore_exception(ETL_ERROR_TEXT("semaphore: out of bounds", ETL_SEMAPHORE_FILE_ID"A"), file_name_, line_number_) + { + } + }; +} // namespace etl + +#endif diff --git a/include/etl/semaphore/semaphore_freertos.h b/include/etl/semaphore/semaphore_freertos.h new file mode 100644 index 00000000..ae4d0e0f --- /dev/null +++ b/include/etl/semaphore/semaphore_freertos.h @@ -0,0 +1,131 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Julien Hecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_SEMAPHORE_FREERTOS_INCLUDED +#define ETL_SEMAPHORE_FREERTOS_INCLUDED + +#include "../platform.h" +#include "../chrono.h" +#include "../error_handler.h" +#include "../limits.h" +#include "../rounded_integral_division.h" +#include "../type_traits.h" +#include "semaphore_exceptions.h" + +#include "FreeRTOS.h" +#include + +namespace etl +{ + //*************************************************************************** + ///\ingroup semaphore + ///\brief This counting_semaphore class is implemented using FreeRTOS's semaphores + //*************************************************************************** + template ::max()> + class counting_semaphore + { + // On FreeRTOS, LeastMaxValue = 0 causes an assert. This deviates slightly from the C++ spec, but it should be fine. + ETL_STATIC_ASSERT(LeastMaxValue > 0, "LeastMaxValue should be 1 or more"); + ETL_STATIC_ASSERT(LeastMaxValue <= numeric_limits::max(), "LeastMaxValue is too high"); + + public: + + explicit counting_semaphore(ptrdiff_t desired) + { + ETL_ASSERT(desired >= 0, ETL_ERROR(semaphore_out_of_bounds)); + sem = xSemaphoreCreateCountingStatic(max(), desired, &semaphore_allocation); + } + + void acquire() + { + // portMAX_DELAY=block forever + // Looping guarantees waiting forever even if the configuration does not have `INCLUDE_vTaskSuspend == 1` + while (xSemaphoreTake(sem, portMAX_DELAY) != pdTRUE); + } + + bool try_acquire() + { + return try_acquire_for(chrono::seconds(0)); + } + + template + bool try_acquire_for(const chrono::duration& rel_time) + { + using URep_t = make_unsigned_t; + + TickType_t ticks_to_wait = 0; + chrono::milliseconds ms_to_wait{rel_time}; + if (ms_to_wait > chrono::duration::zero()) + { + ticks_to_wait = divide_round_to_ceiling(static_cast(ms_to_wait.count()), portTICK_PERIOD_MS); + } + return xSemaphoreTake(sem, ticks_to_wait) == pdTRUE; + } + + template + bool try_acquire_until(const chrono::time_point& abs_time) + { + return try_acquire_for(abs_time - Clock::now()); + } + + void release(ptrdiff_t update = 1) + { + if (update <= 0) + { + return; + } + + // No need to check if the semaphore goes over `max()` because it's guaranteed by FreeRTOS APIs + while (update--) + { + xSemaphoreGive(sem); + } + } + + static constexpr ptrdiff_t max() noexcept + { + return LeastMaxValue; + } + + private: + + // Non-copyable + counting_semaphore(const counting_semaphore&) ETL_DELETE; + counting_semaphore& operator=(const counting_semaphore&) ETL_DELETE; + + // Memory to hold the semaphore + StaticSemaphore_t semaphore_allocation; + + // The semaphore handle itself + SemaphoreHandle_t sem; + }; + + using binary_semaphore = counting_semaphore<1>; +} // namespace etl + +#endif diff --git a/include/etl/semaphore/semaphore_gcc_sync.h b/include/etl/semaphore/semaphore_gcc_sync.h new file mode 100644 index 00000000..15d6b110 --- /dev/null +++ b/include/etl/semaphore/semaphore_gcc_sync.h @@ -0,0 +1,117 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Julien Hecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_SEMAPHORE_GCC_SYNC_INCLUDED +#define ETL_SEMAPHORE_GCC_SYNC_INCLUDED + +#include "../platform.h" +#include "../chrono.h" +#include "../limits.h" +#include "semaphore_exceptions.h" + +namespace etl +{ + //*************************************************************************** + ///\ingroup semaphore + ///\brief This counting_semaphore class is implemented using GCC's __atomic functions. + //*************************************************************************** + template ::max()> + class counting_semaphore + { + public: + + ETL_STATIC_ASSERT(LeastMaxValue >= 0, "LeastMaxValue shall be non-negative"); + ETL_STATIC_ASSERT(LeastMaxValue <= numeric_limits::max(), "LeastMaxValue is too high"); + + ETL_CONSTEXPR14 explicit counting_semaphore(ptrdiff_t desired) + : counter(desired) + { + ETL_ASSERT(desired >= 0 && desired <= max(), ETL_ERROR(semaphore_out_of_bounds)); + } + + void acquire() + { + while (!try_acquire()) + { + } + } + + bool try_acquire() + { + ptrdiff_t old = __atomic_load_n(&counter, __ATOMIC_ACQUIRE); + if (old <= 0) + { + return false; + } + + return __atomic_compare_exchange_n(&counter, &old, old - 1, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED); + } + + template + bool try_acquire_for(const chrono::duration& rel_time) + { + auto now = chrono::steady_clock::now(); + return try_acquire_until(now + rel_time); + } + + template + bool try_acquire_until(const chrono::time_point& abs_time) + { + bool ret = false; + while (!(ret = try_acquire()) && Clock::now() < abs_time) + { + } + return ret; + } + + void release(ptrdiff_t update = 1) + { + ptrdiff_t old = __atomic_fetch_add(&counter, update, __ATOMIC_RELEASE); + + // Preconditions are tested afterwards because otherwise it makes the atomic code needlessly complex. + ETL_ASSERT(update >= 0, ETL_ERROR(semaphore_out_of_bounds)); + ETL_ASSERT(update <= max() - old, ETL_ERROR(semaphore_out_of_bounds)); + } + + static ETL_CONSTEXPR ptrdiff_t max() ETL_NOEXCEPT + { + return LeastMaxValue; + } + + private: + + counting_semaphore(const counting_semaphore&) ETL_DELETE; + counting_semaphore& operator=(const counting_semaphore&) ETL_DELETE; + + ptrdiff_t counter; + }; + + using binary_semaphore = counting_semaphore<1>; +} // namespace etl + +#endif diff --git a/include/etl/semaphore/semaphore_std.h b/include/etl/semaphore/semaphore_std.h new file mode 100644 index 00000000..2daa36d5 --- /dev/null +++ b/include/etl/semaphore/semaphore_std.h @@ -0,0 +1,127 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Julien Hecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_SEMAPHORE_STD_INCLUDED +#define ETL_SEMAPHORE_STD_INCLUDED + +#include "../platform.h" +#include "../chrono.h" + +#include +#include +#include + +namespace etl +{ + //*************************************************************************** + ///\ingroup semaphore + ///\brief These semaphore classes are implemented using std::counting_semaphore and std::binary_semaphore. + //*************************************************************************** + + namespace private_semaphore_std + { + template + struct compat_clock + { + using rep = typename Clock::rep; + using period = std::ratio; + using duration = std::chrono::duration; + using time_point = std::chrono::time_point; + static const bool is_steady = Clock::is_steady; + static time_point now() + { + return time_point{duration{Clock::now().time_since_epoch().count()}}; + } + }; + } // namespace private_semaphore_std + + template ::max()> + class counting_semaphore + { + ETL_STATIC_ASSERT(LeastMaxValue > 0, "LeastMaxValue should be 1 or more"); + ETL_STATIC_ASSERT(LeastMaxValue <= std::counting_semaphore<>::max(), "LeastMaxValue is too high"); + + public: + + explicit counting_semaphore(ptrdiff_t desired) + : sem(desired) + { + } + + void acquire() + { + return sem.acquire(); + } + + bool try_acquire() + { + return sem.try_acquire(); + } + + template + bool try_acquire_for(const etl::chrono::duration& rel_time) + { + std::chrono::duration> duration(rel_time.count()); + + return sem.try_acquire_for(duration); + } + + template + bool try_acquire_until(const etl::chrono::time_point& abs_time) + { + using CompatDuration = std::chrono::duration>; + + std::chrono::time_point, CompatDuration> time_point{ + CompatDuration(abs_time.time_since_epoch().count())}; + + return sem.try_acquire_until(time_point); + } + + void release(ptrdiff_t update = 1) + { + return sem.release(update); + } + + static constexpr ptrdiff_t max() noexcept + { + return LeastMaxValue; + } + + private: + + // Non-copyable + counting_semaphore(const counting_semaphore&) ETL_DELETE; + counting_semaphore& operator=(const counting_semaphore&) ETL_DELETE; + + std::counting_semaphore sem; + }; + + using binary_semaphore = counting_semaphore<1>; +} // namespace etl + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a03b5c5c..d4feeb0e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -13,6 +13,7 @@ project(etl_unit_tests LANGUAGES CXX) add_executable(etl_tests main.cpp murmurhash3.cpp + clocks_for_unit_tests.cpp test_algorithm.cpp test_algorithm_ranges.cpp test_alignment.cpp @@ -298,6 +299,7 @@ add_executable(etl_tests test_rms.cpp test_rounded_integral_division.cpp test_scaled_rounding.cpp + test_semaphore.cpp test_set.cpp test_shared_message.cpp test_signal.cpp diff --git a/test/clocks_for_unit_tests.cpp b/test/clocks_for_unit_tests.cpp new file mode 100644 index 00000000..157ef3a4 --- /dev/null +++ b/test/clocks_for_unit_tests.cpp @@ -0,0 +1,59 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Julien Hecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#include "clocks_for_unit_tests.h" +#include "etl/atomic.h" +#include "etl/chrono.h" + +namespace test +{ + namespace clocks + { + CLOCK_TYPE nanoseconds{0}; + CLOCK_TYPE milliseconds{0}; + CLOCK_TYPE seconds{0}; + } // namespace clocks +} // namespace test + +extern "C" +{ + etl::chrono::system_clock::rep etl_get_system_clock() + { + return etl::chrono::system_clock::rep(test::clocks::milliseconds); + } + + etl::chrono::high_resolution_clock::rep etl_get_high_resolution_clock() + { + return etl::chrono::high_resolution_clock::rep(test::clocks::nanoseconds); + } + + etl::chrono::steady_clock::rep etl_get_steady_clock() + { + return etl::chrono::steady_clock::rep(test::clocks::seconds); + } +} diff --git a/test/clocks_for_unit_tests.h b/test/clocks_for_unit_tests.h new file mode 100644 index 00000000..b2630a4e --- /dev/null +++ b/test/clocks_for_unit_tests.h @@ -0,0 +1,54 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Julien Hecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_TEST_CLOCKS_INCLUDED +#define ETL_TEST_CLOCKS_INCLUDED + +#include "etl/platform.h" +#include "etl/atomic.h" + +#if defined(ETL_HAS_ATOMIC) && ETL_HAS_ATOMIC == 1 + // In general it is more correct to use atomics to keep this value since it + // may be used and modified across multiple threads (see test_semaphore.cpp). + #define CLOCK_TYPE etl::atomic +#else + // However, in practice, a data race here isn't really an issue so int is fine too. + #define CLOCK_TYPE int +#endif + +namespace test +{ + namespace clocks + { + extern CLOCK_TYPE nanoseconds; + extern CLOCK_TYPE milliseconds; + extern CLOCK_TYPE seconds; + } // namespace clocks +} // namespace test + +#endif diff --git a/test/maincpp03check.cpp b/test/maincpp03check.cpp index 2c5c9936..847ef37b 100644 --- a/test/maincpp03check.cpp +++ b/test/maincpp03check.cpp @@ -136,6 +136,7 @@ SOFTWARE. #include "etl/reference_flat_set.h" #include "etl/scaled_rounding.h" #include "etl/scheduler.h" +#include "etl/semaphore.h" #include "etl/set.h" #include "etl/smallest.h" #include "etl/sqrt.h" diff --git a/test/meson.build b/test/meson.build index 16890071..2386330a 100644 --- a/test/meson.build +++ b/test/meson.build @@ -1,5 +1,6 @@ etl_test_sources = files( 'main.cpp', + 'clocks_for_unit_tests.cpp', 'murmurhash3.cpp', 'test_algorithm.cpp', 'test_algorithm_ranges.cpp', @@ -286,6 +287,7 @@ etl_test_sources = files( 'test_rms.cpp', 'test_rounded_integral_division.cpp', 'test_scaled_rounding.cpp', + 'test_semaphore.cpp', 'test_set.cpp', 'test_shared_message.cpp', 'test_signal.cpp', diff --git a/test/syntax_check/CMakeLists.txt b/test/syntax_check/CMakeLists.txt index a8964e31..928e7974 100644 --- a/test/syntax_check/CMakeLists.txt +++ b/test/syntax_check/CMakeLists.txt @@ -343,6 +343,7 @@ target_sources(tests PRIVATE rounded_integral_division.h.t.cpp scaled_rounding.h.t.cpp scheduler.h.t.cpp + semaphore.h.t.cpp set.h.t.cpp shared_message.h.t.cpp signal.h.t.cpp diff --git a/test/syntax_check/semaphore.h.t.cpp b/test/syntax_check/semaphore.h.t.cpp new file mode 100644 index 00000000..a3cb50e0 --- /dev/null +++ b/test/syntax_check/semaphore.h.t.cpp @@ -0,0 +1,29 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Julien Hecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#include diff --git a/test/test_chrono_clocks.cpp b/test/test_chrono_clocks.cpp index 6fefc751..982332d5 100644 --- a/test/test_chrono_clocks.cpp +++ b/test/test_chrono_clocks.cpp @@ -30,39 +30,14 @@ SOFTWARE. #include "etl/platform.h" +#include "clocks_for_unit_tests.h" #include "unit_test_framework.h" #include "etl/chrono.h" #include -//***************************************************************************** -// Global clock functions. -//***************************************************************************** -namespace -{ - int nanoseconds = 0; - int milliseconds = 0; - int seconds = 0; -} // namespace - -extern "C" -{ - etl::chrono::system_clock::rep etl_get_system_clock() - { - return etl::chrono::system_clock::rep(milliseconds); - } - - etl::chrono::high_resolution_clock::rep etl_get_high_resolution_clock() - { - return etl::chrono::high_resolution_clock::rep(nanoseconds); - } - - etl::chrono::steady_clock::rep etl_get_steady_clock() - { - return etl::chrono::steady_clock::rep(seconds); - } -} +using namespace test::clocks; namespace { diff --git a/test/test_etl_traits.cpp b/test/test_etl_traits.cpp index a79f2d82..06c415cc 100644 --- a/test/test_etl_traits.cpp +++ b/test/test_etl_traits.cpp @@ -33,6 +33,7 @@ SOFTWARE. #include "etl/platform.h" #include "etl/char_traits.h" #include "etl/mutex.h" +#include "etl/semaphore.h" #include "etl/version.h" namespace @@ -107,6 +108,7 @@ namespace // Has... CHECK_EQUAL((ETL_HAS_ATOMIC == 1), etl::traits::has_atomic); CHECK_EQUAL((ETL_HAS_MUTEX == 1), etl::traits::has_mutex); + CHECK_EQUAL((ETL_HAS_SEMAPHORE == 1), etl::traits::has_semaphore); CHECK_EQUAL((ETL_HAS_NULLPTR == 1), etl::traits::has_nullptr); CHECK_EQUAL((ETL_HAS_CHAR8_T == 1), etl::traits::has_char8_t); CHECK_EQUAL((ETL_HAS_NATIVE_CHAR8_T == 1), etl::traits::has_native_char8_t); diff --git a/test/test_semaphore.cpp b/test/test_semaphore.cpp new file mode 100644 index 00000000..10ef781f --- /dev/null +++ b/test/test_semaphore.cpp @@ -0,0 +1,193 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 Julien Hecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#include "clocks_for_unit_tests.h" +#include "unit_test_framework.h" + +#include + +#include + +using namespace test::clocks; + +#if ETL_USING_ATOMIC + +namespace +{ + SUITE(test_semaphore) + { + TEST(test_semaphore_counting_nominal) + { + int initial_count = 10; + int release_count = 20; + int acquire_count = initial_count + release_count; + + etl::counting_semaphore<> sem{initial_count}; + + std::thread thread( + [&sem, &release_count]() + { + while (release_count > 0) + { + sem.release(); + release_count--; + } + }); + + while (acquire_count > 0) + { + sem.acquire(); + acquire_count--; + } + + thread.join(); + + CHECK_EQUAL(release_count, 0); + CHECK_EQUAL(acquire_count, 0); + CHECK_FALSE(sem.try_acquire()); + } + + TEST(test_semaphore_counting_try_acquire) + { + etl::counting_semaphore<> sem{1}; + + CHECK_TRUE(sem.try_acquire()); + CHECK_FALSE(sem.try_acquire()); + } + + TEST(test_semaphore_counting_try_for) + { + etl::counting_semaphore<> sem{1}; + + // try_for uses steady_clock in the `gcc_sync` implementation + // (default backend if STL is not available or FreeRTOS is not enabled) + // During testing, this means the clock used for waiting has a granularity of 1s. + seconds = 0; + std::thread thread( + []() + { + // Just sleep a bit, not literally 1s, because that's a wait of time + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + seconds++; + }); + + CHECK_TRUE(sem.try_acquire_for(etl::chrono::milliseconds(10))); + CHECK_FALSE(sem.try_acquire_for(etl::chrono::milliseconds(10))); + + thread.join(); + } + + TEST(test_semaphore_counting_try_until) + { + etl::counting_semaphore<> sem{1}; + + nanoseconds = 0; + std::thread thread( + []() + { + // Count to 20 milliseconds + for (int i = 0; i < 20; i++) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + nanoseconds += 1000000; + } + }); + + auto now = etl::chrono::high_resolution_clock::now(); + CHECK_TRUE(sem.try_acquire_until(now + etl::chrono::milliseconds(10))); + CHECK_FALSE(sem.try_acquire_until(now + etl::chrono::milliseconds(10))); + + thread.join(); + } + + TEST(test_semaphore_binary_nominal) + { + etl::binary_semaphore sem{0}; + + sem.release(); + sem.acquire(); + + CHECK_FALSE(sem.try_acquire()); + } + + TEST(test_semaphore_binary_try_acquire) + { + etl::binary_semaphore sem{1}; + + CHECK_TRUE(sem.try_acquire()); + CHECK_FALSE(sem.try_acquire()); + } + + TEST(test_semaphore_binary_try_for) + { + etl::binary_semaphore sem{1}; + + // try_for uses steady_clock in the `gcc_sync` implementation + // (default backend if STL is not available or FreeRTOS is not enabled) + // During testing, this means the clock used for waiting has a granularity of 1s. + seconds = 0; + std::thread thread( + []() + { + // Just sleep a bit, not literally 1s, because that's a waste of time + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + seconds++; + }); + + CHECK_TRUE(sem.try_acquire_for(etl::chrono::milliseconds(10))); + CHECK_FALSE(sem.try_acquire_for(etl::chrono::milliseconds(10))); + + thread.join(); + } + + TEST(test_semaphore_binary_try_until) + { + etl::binary_semaphore sem{1}; + + nanoseconds = 0; + std::thread thread( + []() + { + // Count to 20 milliseconds + for (int i = 0; i < 20; i++) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + nanoseconds += 1000000; + } + }); + + auto now = etl::chrono::high_resolution_clock::now(); + CHECK_TRUE(sem.try_acquire_until(now + etl::chrono::milliseconds(10))); + CHECK_FALSE(sem.try_acquire_until(now + etl::chrono::milliseconds(10))); + + thread.join(); + } + } +} // namespace + +#endif diff --git a/test/vs2022/etl.vcxproj b/test/vs2022/etl.vcxproj index e7bb1ff5..5cb41382 100644 --- a/test/vs2022/etl.vcxproj +++ b/test/vs2022/etl.vcxproj @@ -3696,6 +3696,12 @@ + + + + + + @@ -4040,6 +4046,7 @@ + @@ -4076,6 +4083,7 @@ + true true @@ -11405,6 +11413,7 @@ + true true @@ -11825,4 +11834,4 @@ - \ No newline at end of file + diff --git a/test/vs2022/etl.vcxproj.filters b/test/vs2022/etl.vcxproj.filters index 954b7de1..3044eab1 100644 --- a/test/vs2022/etl.vcxproj.filters +++ b/test/vs2022/etl.vcxproj.filters @@ -226,6 +226,9 @@ {5b809d95-b37c-4011-af5e-b5ea6dcb354e} + + {304d989b-df11-403b-8ce9-ea594a66e759} + @@ -558,6 +561,9 @@ ETL\Utilities + + ETL\Utilities + ETL\Maths @@ -1098,6 +1104,12 @@ Tests\Test Support + + Tests\Test Support + + + Tests\Test Support + Tests\Test Support @@ -1566,6 +1578,21 @@ UnitTest++\Header Files + + ETL\Utilities\Semaphore + + + ETL\Utilities\Semaphore + + + ETL\Utilities\Semaphore + + + ETL\Utilities\Semaphore + + + ETL\Utilities\Semaphore + @@ -2021,6 +2048,9 @@ Tests\Strings + + Tests + Tests\Containers @@ -4121,4 +4151,4 @@ Resource Files\Images - \ No newline at end of file +