From ce99af6e44973e364873532225d3b3894f651689 Mon Sep 17 00:00:00 2001 From: Marika Lehmann Date: Mon, 30 May 2022 16:32:43 +0200 Subject: [PATCH 1/2] iox-#337 Adapt mepoo integration test Use std::chrono::steady_clock instead of PosixTimer Signed-off-by: Marika Lehmann --- .../test/integrationtests/test_posh_mepoo.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp b/iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp index 73597f3eec..0032b5ab38 100644 --- a/iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp +++ b/iceoryx_posh/test/integrationtests/test_posh_mepoo.cpp @@ -17,7 +17,6 @@ #include "iceoryx_hoofs/cxx/optional.hpp" #include "iceoryx_hoofs/internal/units/duration.hpp" -#include "iceoryx_hoofs/posix_wrapper/timer.hpp" #include "iceoryx_hoofs/testing/timing_test.hpp" #include "iceoryx_posh/error_handling/error_handling.hpp" #include "iceoryx_posh/mepoo/mepoo_config.hpp" @@ -41,8 +40,6 @@ using namespace iox::units::duration_literals; using iox::mepoo::MePooConfig; using iox::roudi::RouDiEnvironment; -using iox::posix::Timer; - class Mepoo_IntegrationTest : public Test { public: @@ -449,18 +446,18 @@ TIMING_TEST_F(Mepoo_IntegrationTest, MempoolCreationTimeDefaultConfig, Repeat(5) MemPoolInfoContainer memPoolTestContainer; auto testMempoolConfig = defaultMemPoolConfig(); - auto start = Timer::now(); + auto start = std::chrono::steady_clock::now(); SetUp(memPoolTestContainer, testMempoolConfig, configType::DEFAULT); - auto stop = Timer::now(); + auto stop = std::chrono::steady_clock::now(); // Calc the difference - iox::units::Duration timediff = stop.value() - start.value(); + auto timediff = iox::units::Duration(stop - start); PrintTiming(timediff); - /// Currently we expect that the RouDi is ready at least after 2 seconds + // Currently we expect that the RouDi is ready at least after 2 seconds auto maxtime = 2000_ms; EXPECT_THAT(timediff, Le(maxtime)); }); From 93ab2300c01ac2d1c438a070d5b3e4cf37b993b7 Mon Sep 17 00:00:00 2001 From: Marika Lehmann Date: Mon, 30 May 2022 16:59:24 +0200 Subject: [PATCH 2/2] iox-#337 Remove iox::posix::Timer Signed-off-by: Marika Lehmann --- .../release-notes/iceoryx-unreleased.md | 1 + iceoryx_hoofs/CMakeLists.txt | 1 - iceoryx_hoofs/README.md | 5 +- .../iceoryx_hoofs/posix_wrapper/timer.hpp | 323 ---------- iceoryx_hoofs/source/posix_wrapper/timer.cpp | 567 ------------------ .../test/moduletests/test_posix_timer.cpp | 505 ---------------- .../iceoryx_hoofs/testing/timing_test.hpp | 12 +- 7 files changed, 10 insertions(+), 1404 deletions(-) delete mode 100644 iceoryx_hoofs/include/iceoryx_hoofs/posix_wrapper/timer.hpp delete mode 100644 iceoryx_hoofs/source/posix_wrapper/timer.cpp delete mode 100644 iceoryx_hoofs/test/moduletests/test_posix_timer.cpp diff --git a/doc/website/release-notes/iceoryx-unreleased.md b/doc/website/release-notes/iceoryx-unreleased.md index d92bcf340e..ddc6c21245 100644 --- a/doc/website/release-notes/iceoryx-unreleased.md +++ b/doc/website/release-notes/iceoryx-unreleased.md @@ -37,6 +37,7 @@ - Builder pattern extracted from `helplets.hpp` into `design_pattern/builder.hpp` - Uninteresting mock function calls in tests [\#1341](https://github.com/eclipse-iceoryx/iceoryx/issues/1341) - `cxx::unique_ptr` owns deleter, remove all deleter classes [\#1143](https://github.com/eclipse-iceoryx/iceoryx/issues/1143) +- Remove `iox::posix::Timer` [\#337](https://github.com/eclipse-iceoryx/iceoryx/issues/337) **New API features:** diff --git a/iceoryx_hoofs/CMakeLists.txt b/iceoryx_hoofs/CMakeLists.txt index 3117e8845e..651666ba93 100644 --- a/iceoryx_hoofs/CMakeLists.txt +++ b/iceoryx_hoofs/CMakeLists.txt @@ -112,7 +112,6 @@ add_library(iceoryx_hoofs source/posix_wrapper/signal_watcher.cpp source/posix_wrapper/system_configuration.cpp source/posix_wrapper/thread.cpp - source/posix_wrapper/timer.cpp source/posix_wrapper/unnamed_semaphore.cpp source/posix_wrapper/unix_domain_socket.cpp source/relocatable_pointer/base_relative_pointer.cpp diff --git a/iceoryx_hoofs/README.md b/iceoryx_hoofs/README.md index aa1209f6a2..4a65de0e1b 100644 --- a/iceoryx_hoofs/README.md +++ b/iceoryx_hoofs/README.md @@ -155,7 +155,6 @@ abstractions or add a new one when using POSIX resources like semaphores, shared |`SharedMemoryObject` | i | | Creates and maps existing shared memory into the application. | |`system_configuration` | i | | Collection of free functions which acquire system information like the page-size. | |`thread` | | | Wrapper for pthread functions like `pthread_setname_np`. | -|`Timer` | | X | Interface for the posix timer, see [ManPage timer_create](https://www.man7.org/linux/man-pages/man2/timer_create.2.html). | |`UnixDomainSocket` | i | | Interface for unix domain sockets. | ### Units @@ -163,11 +162,13 @@ abstractions or add a new one when using POSIX resources like semaphores, shared Never use physical properties like speed or time directly as integer or float in your code. Otherwise you encounter problems like this function `void setTimeout(int timeout)`. What is the unit of the argument, seconds? minutes? If you use `Duration` you see it directly in the code. -``` +```cpp + void setTimeout(const Duration & timeout); setTimeout(11_s); // 11 seconds setTimeout(5_ms); // 5 milliseconds + ``` | class | internal | maybe obsolete | description | diff --git a/iceoryx_hoofs/include/iceoryx_hoofs/posix_wrapper/timer.hpp b/iceoryx_hoofs/include/iceoryx_hoofs/posix_wrapper/timer.hpp deleted file mode 100644 index cb92de741b..0000000000 --- a/iceoryx_hoofs/include/iceoryx_hoofs/posix_wrapper/timer.hpp +++ /dev/null @@ -1,323 +0,0 @@ -// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. -// Copyright (c) 2021 by Apex.AI Inc. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 -#ifndef IOX_HOOFS_POSIX_WRAPPER_TIMER_HPP -#define IOX_HOOFS_POSIX_WRAPPER_TIMER_HPP - -#include "iceoryx_hoofs/cxx/optional.hpp" -#include "iceoryx_hoofs/cxx/vector.hpp" -#include "iceoryx_hoofs/design_pattern/creation.hpp" -#include "iceoryx_hoofs/internal/units/duration.hpp" -#include "iceoryx_hoofs/platform/signal.hpp" -#include "iceoryx_hoofs/platform/time.hpp" - -#include -#include -#include -#include -#include -#include - - -namespace iox -{ -namespace posix -{ -enum class TimerError -{ - NO_ERROR, - TIMER_NOT_INITIALIZED, - NO_VALID_CALLBACK, - KERNEL_ALLOC_FAILED, - INVALID_ARGUMENTS, - ALLOC_MEM_FAILED, - NO_PERMISSION, - INVALID_POINTER, - NO_TIMER_TO_DELETE, - TIMEOUT_IS_ZERO, - INTERNAL_LOGIC_ERROR -}; - -using namespace iox::units::duration_literals; - - -/// @brief Interface for timers on POSIX operating systems -/// @note Can't be copied or moved as operating system has a pointer to this object. It needs to be ensured that this -/// object lives longer than timeToWait, otherwise the operating system will unregister the timer -/// @concurrent not thread safe -/// -/// @code -/// posix::Timer TiborTheTimer{100_ms, [&]() { fooBar++; }}; -/// -/// // Start a periodic timer -/// TiborTheTimer.start(true); -/// // [.. wait ..] -/// // Timer fires after 100_ms and calls the lambda which increments fooBar -/// -/// TiborTheTimer.stop(); -/// -/// @endcode - -/// This class will be DEPRECATED in the near future. In its current form there may still be potential races when -/// start/stop/restart are called concurrently (this includes the callback, which is executed in a separate thread). -/// The implementation also has too much overhead in the callback execution (due to execution logic and potentially -/// multiple callback threads). -/// -/// It will be replaced with simpler versions for individual use cases, such as a CountdownTimer which can be used for -/// watchdog/keepalive purposes. -class Timer -{ - public: - enum class RunMode - { - ONCE, - PERIODIC - }; - - /// @brief - /// defines the behavior of the timer when the callback runtime is greater - /// than the periodic trigger time. - /// SKIP_TO_NEXT_BEAT skip callback and call it in the next cycle - /// IMMEDIATE call the callback right after the currently running callback is finished - /// TERMINATE terminates the process by calling the errorHandler with - /// POSIX_TIMER__CALLBACK_RUNTIME_EXCEEDS_RETRIGGER_TIME - enum class CatchUpPolicy - { - SKIP_TO_NEXT_BEAT, - IMMEDIATE, - TERMINATE - }; - - private: - static constexpr size_t SIZE_OF_COMBINDED_INDEX_AND_DESCRIPTOR = sizeof(uint32_t); - static constexpr size_t SIZE_OF_SIGVAL_INT = sizeof(int); - static_assert(SIZE_OF_SIGVAL_INT >= SIZE_OF_COMBINDED_INDEX_AND_DESCRIPTOR, "size of sigval_int is to low"); - - static constexpr uint32_t MAX_NUMBER_OF_CALLBACK_HANDLES = 100u; - static_assert(MAX_NUMBER_OF_CALLBACK_HANDLES <= std::numeric_limits::max(), - "number of callback handles exceeds max index value"); - class OsTimer; - struct OsTimerCallbackHandle - { - static constexpr uint32_t MAX_DESCRIPTOR_VALUE{(1u << 24u) - 1u}; - static sigval indexAndDescriptorToSigval(uint8_t index, uint32_t descriptor) noexcept; - static uint8_t sigvalToIndex(sigval intVal) noexcept; - static uint32_t sigvalToDescriptor(sigval intVal) noexcept; - - void incrementDescriptor() noexcept; - - std::mutex m_accessMutex; - - /// @brief the descriptor is unique for a timer_t in OsTimer, if this handle is recycled, the descriptor needs - /// to be incremented first - std::atomic m_descriptor{0u}; - // must be operator= otherwise it is undefined, see https://en.cppreference.com/w/cpp/atomic/ATOMIC_FLAG_INIT - std::atomic_flag m_callbackIsAboutToBeExecuted = ATOMIC_FLAG_INIT; - - std::atomic m_inUse{false}; - std::atomic m_isTimerActive{false}; - std::atomic m_timerInvocationCounter{0u}; - CatchUpPolicy m_catchUpPolicy{CatchUpPolicy::TERMINATE}; - OsTimer* m_timer{nullptr}; - }; - - /// This class will be DEPRECATED in the near future. - class OsTimer - { -#ifdef __QNX__ - static constexpr timer_t INVALID_TIMER_ID = 0; -#else - static constexpr timer_t INVALID_TIMER_ID = nullptr; -#endif - public: - /// @brief Wrapper that can be registered with the operating system - static void callbackHelper(sigval data) noexcept; - - OsTimer(const units::Duration timeToWait, const std::function& callback) noexcept; - - OsTimer(const OsTimer&) = delete; - OsTimer(OsTimer&&) = delete; - OsTimer& operator=(const OsTimer&) = delete; - OsTimer& operator=(OsTimer&&) = delete; - - /// @brief D'tor - virtual ~OsTimer() noexcept; - - /// @brief Starts the timer - /// - /// The callback is called by the operating system after the time has expired. - /// - /// @param[in] runMode can be a periodic timer if set to RunMode::PERIODIC or - /// it runs just once when it is set to RunMode::ONCE - /// @param[in] CatchUpPolicy define behavior when callbackRuntime > timeToWait - /// @note Shall only be called when callback is given - cxx::expected start(const RunMode runMode, const CatchUpPolicy catchUpPolicy) noexcept; - - /// @brief Disarms the timer - /// @note Shall only be called when callback is given, guarantee after stop() call is callback is immediately - /// called or never at all - cxx::expected stop() noexcept; - - /// @brief Disarms the timer, assigns a new timeToWait value and arms the timer - /// @note Shall only be called when callback is given - /// @param[in] runMode periodic can be a periodic timer if set to RunMode::PERIODIC or - /// once when in RunMode::ONCE - /// @param[in] CatchUpPolicy define behavior when callbackRuntime > timeToWait - cxx::expected - restart(const units::Duration timeToWait, const RunMode runMode, const CatchUpPolicy catchUpPolicy) noexcept; - - // @brief Returns the time until the timer expires the next time - /// @note Shall only be called when callback is given - cxx::expected timeUntilExpiration() noexcept; - - /// @brief In case the callback is not immediately called by the operating system, getOverruns() returns the - /// additional overruns that happended in the delay interval - /// @note Shall only be called when callback is given - cxx::expected getOverruns() noexcept; - - /// @brief Returns true if the construction of the object was successful - bool hasError() const noexcept; - - /// @brief Returns the error that occured on constructing the object - TimerError getError() const noexcept; - - private: - /// @brief Call the user-defined callback - /// @note This call is wrapped in a plain C function - void executeCallback() noexcept; - - private: - /// @brief Duration after the timer calls the user-defined callback function - units::Duration m_timeToWait; - - /// @brief Stores the user-defined callback - std::function m_callback; - - /// @brief Identifier for the timer in the operating system - timer_t m_timerId{INVALID_TIMER_ID}; - - uint8_t m_callbackHandleIndex{0u}; - - /// @todo will be obsolete with creation pattern - /// @brief Bool that signals whether the object is fully initalized - bool m_isInitialized{false}; - - /// @todo creation pattern - /// @brief If an error happened during creation the value is stored in here - TimerError m_errorValue{TimerError::NO_ERROR}; - - static OsTimerCallbackHandle s_callbackHandlePool[MAX_NUMBER_OF_CALLBACK_HANDLES]; - }; - - public: - /// @brief Creates a timer without an operating system callback - /// - /// Creates a light-weight timer object that can be used with - /// * hasExpiredComparedToCreationTime() - /// * resetCreationTime() - /// - /// @param[in] timeToWait - How long should be waited? - /// @note Does not set up an operating system timer, but uses CLOCK_REALTIME instead - /// @todo refactor this cTor and its functionality to a class called StopWatch - Timer(const units::Duration timeToWait) noexcept; - - /// @brief Creates a timer with an operating system callback - /// - /// Initially the timer is stopped. - /// - /// @param[in] timeToWait - How long should be waited? - /// @param[in] callback - Function called after timeToWait (User needs to ensure lifetime of function till stop() - /// call) - /// @note Operating systems needs a valid reference to this object, hence DesignPattern::Creation can't be used - Timer(const units::Duration timeToWait, const std::function& callback) noexcept; - - /// @brief creates Duration from the result of clock_gettime(CLOCK_REALTIME, ...) - /// @return if the clock_gettime call failed TimerError is returned otherwise Duration - /// @todo maybe move this to a clock implementation? - static cxx::expected now() noexcept; - - /// @brief Move or semantics are forbidden as address of object is not allowed to change - Timer(const Timer& other) = delete; - - /// @brief Move or semantics are forbidden as address of object is not allowed to change - Timer(Timer&& other) = delete; - - /// @brief Move or semantics are forbidden as address of object is not allowed to change - Timer& operator=(const Timer& other) = delete; - - /// @brief Move or semantics are forbidden as address of object is not allowed to change - Timer& operator=(Timer&& other) = delete; - - /// @brief D'tor - virtual ~Timer() noexcept = default; - - /// @brief Starts the timer - /// - /// The callback is called by the operating system after the time has expired. - /// - /// @param[in] runMode for continuous callbacks PERIODIC otherwise ONCE - /// @param[in] CatchUpPolicy define behavior when callbackRuntime > timeToWait - /// @note Shall only be called when callback is given - cxx::expected start(const RunMode runMode, const CatchUpPolicy catchUpPolicy) noexcept; - - /// @brief Disarms the timer - /// @note Shall only be called when callback is given, guarantee after stop() call is callback is immediately - /// called or never at all - cxx::expected stop() noexcept; - - /// @brief Disarms the timer, assigns a new timeToWait value and arms the timer - /// @param[in] timeToWait duration till the callback should be called - /// @param[in] runMode for continuous callbacks PERIODIC otherwise ONCE - /// @param[in] CatchUpPolicy define behavior when callbackRuntime > timeToWait - /// @note Shall only be called when callback is given - cxx::expected - restart(const units::Duration timeToWait, const RunMode runMode, const CatchUpPolicy catchUpPolicy) noexcept; - - // @brief Returns the time until the timer expires the next time - /// @note Shall only be called when callback is given - cxx::expected timeUntilExpiration() noexcept; - - /// @brief In case the callback is not immediately called by the operating system, getOverruns() returns the - /// additional overruns that happended in the delay interval - /// @note Shall only be called when callback is given - cxx::expected getOverruns() noexcept; - - /// @brief Returns true if the construction of the object was successful - bool hasError() const noexcept; - - /// @brief Returns the error that occured on constructing the object - TimerError getError() const noexcept; - - private: - cxx::optional m_osTimer; - - /// @brief Converts errnum to TimerError - static cxx::error createErrorFromErrno(const int32_t errnum) noexcept; - - /// @brief Duration after the timer calls the user-defined callback function - units::Duration m_timeToWait; - - /// @brief Time when the timer object was created - units::Duration m_creationTime; - - /// @brief If an error happened during creation the value is stored in here - TimerError m_errorValue{TimerError::NO_ERROR}; -}; - -} // namespace posix -} // namespace iox - -#endif // IOX_HOOFS_POSIX_WRAPPER_TIMER_HPP diff --git a/iceoryx_hoofs/source/posix_wrapper/timer.cpp b/iceoryx_hoofs/source/posix_wrapper/timer.cpp deleted file mode 100644 index 18658a5af0..0000000000 --- a/iceoryx_hoofs/source/posix_wrapper/timer.cpp +++ /dev/null @@ -1,567 +0,0 @@ -// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. -// Copyright (c) 2021 - 2022 by Apex.AI Inc. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -#include "iceoryx_hoofs/posix_wrapper/timer.hpp" -#include "iceoryx_hoofs/cxx/generic_raii.hpp" -#include "iceoryx_hoofs/platform/platform_correction.hpp" -#include "iceoryx_hoofs/posix_wrapper/posix_call.hpp" -#include - -namespace iox -{ -namespace posix -{ -Timer::OsTimerCallbackHandle Timer::OsTimer::s_callbackHandlePool[MAX_NUMBER_OF_CALLBACK_HANDLES]; - -sigval Timer::OsTimerCallbackHandle::indexAndDescriptorToSigval(uint8_t index, uint32_t descriptor) noexcept -{ - assert(descriptor < MAX_DESCRIPTOR_VALUE); - // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) shift 8 bits - uint32_t temp = (descriptor << 8) | static_cast(index); - sigval sigvalData{}; - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) system struct - sigvalData.sival_int = static_cast(temp); - return sigvalData; -} - -uint8_t Timer::OsTimerCallbackHandle::sigvalToIndex(sigval intVal) noexcept -{ - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access, cppcoreguidelines-avoid-magic-numbers) system struct - return static_cast(0xFF & intVal.sival_int); -} - -uint32_t Timer::OsTimerCallbackHandle::sigvalToDescriptor(sigval intVal) noexcept -{ - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) system struct - auto temp = static_cast(intVal.sival_int); - // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) shift 8 bits - return (temp >> 8U) & 0xFFFFFFU; -} - -void Timer::OsTimerCallbackHandle::incrementDescriptor() noexcept -{ - auto callbackHandleDescriptor = m_descriptor.load(std::memory_order_relaxed); - callbackHandleDescriptor++; - if (callbackHandleDescriptor >= Timer::OsTimerCallbackHandle::MAX_DESCRIPTOR_VALUE) - { - callbackHandleDescriptor = 0U; - } - - m_descriptor.store(callbackHandleDescriptor, std::memory_order_relaxed); -} - -void Timer::OsTimer::callbackHelper(sigval data) noexcept -{ - uint32_t index = Timer::OsTimerCallbackHandle::sigvalToIndex(data); - auto descriptor = Timer::OsTimerCallbackHandle::sigvalToDescriptor(data); - - if (index >= Timer::OsTimerCallbackHandle::MAX_DESCRIPTOR_VALUE) - { - ///@todo decide if to print a warning - return; - } - - auto& handle = OsTimer::s_callbackHandlePool[index]; - - // small optimization to not lock the mutex if the callback handle is not valid anymore - if (descriptor != handle.m_descriptor.load(std::memory_order::memory_order_relaxed)) - { - return; - } - - // signal that we intend to call the callback (but we may not start it ourself, it may be done - // by another invocation of this function) - // NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores) used in a do-while loop below - auto invocationCounter = handle.m_timerInvocationCounter.fetch_add(1U, std::memory_order_relaxed); - - // avoid the spurious failures of try_lock with this flag end still reduce contention - // if another thread is in this section we return but we already stated our intention to call the callback - // via m_timerInvocationCounter and the other thread will do this for us - // note that thte invocation may be lost if the callback is already running, this is intentional - // to avoid an unlimited number of callback invocations to pile up - - // note: if we can tolerate those calls to pile up we can get rid of this flag and the while loop as well - - if (!handle.m_callbackIsAboutToBeExecuted.test_and_set(std::memory_order_acq_rel)) - { - // flag protected region - - // the mutex is needed to protect against concurrent deletion of the timer in the destructor - std::lock_guard lockGuard(handle.m_accessMutex); - - // clear the flag regardless of how we leave the function - cxx::GenericRAII clearCallbackFlag( - []() {}, [&]() { handle.m_callbackIsAboutToBeExecuted.clear(std::memory_order_release); }); - - do - { - // prohibits other threads from entering the flag protected region - handle.m_callbackIsAboutToBeExecuted.test_and_set(std::memory_order_acq_rel); - - cxx::Expects((handle.m_timer != nullptr) && "Timer in inconsistent state"); - - if (!handle.m_inUse.load(std::memory_order::memory_order_relaxed)) - { - return; - } - - if (descriptor != handle.m_descriptor.load(std::memory_order::memory_order_relaxed)) - { - return; - } - - if (!handle.m_isTimerActive.load(std::memory_order::memory_order_relaxed)) - { - return; - } - - invocationCounter = handle.m_timerInvocationCounter.exchange(0U, std::memory_order_acq_rel); - - // did someone else execute the callback for us? - if (invocationCounter != 0U) - { - handle.m_timer->executeCallback(); - } - - handle.m_callbackIsAboutToBeExecuted.clear(std::memory_order_release); - // another thread can try to enter the flag protected region by setting the flag now but will still need to - // wait for the mutex (they can pile up in theory) - // the point is: it must be set to false before the counter comparison of the while loop, - // otherwise: another thread could increment the counter *after* this - // comparison, see the flag is still true, leave and rely on this thread to perform the callback, but for - // this thread the comparison has seen 0 and it will therefore leave the loop - - // by clearing the flag temporarily this cannot happen (but it has other drawbacks) - - // get the latest value (can be outdated after the call, but this is not important here, - // we just need updates from concurrent threads which may have incremented the counter) - handle.m_timerInvocationCounter.compare_exchange_strong( - invocationCounter, invocationCounter, std::memory_order_acq_rel, std::memory_order_acquire); - - // if the counter is positive it means some other thread has incremented it in the meantime - // otherwise some thread may be about to do so but then will either find the flag to be cleared - // and be able to enter the region or the flag to be set again, in which case this thread - // will take care of the call - - } while (handle.m_catchUpPolicy == CatchUpPolicy::IMMEDIATE && invocationCounter > 0U); - } - else - { - cxx::Ensures((handle.m_catchUpPolicy != CatchUpPolicy::TERMINATE) - && "Timer callback runtime exceeds retrigger time"); - } -} - -Timer::OsTimer::OsTimer(const units::Duration timeToWait, const std::function& callback) noexcept - : m_timeToWait(timeToWait) - , m_callback(callback) -{ - // Is the callback valid? - if (!callback) - { - m_isInitialized = false; - m_errorValue = TimerError::NO_VALID_CALLBACK; - return; - } - - // find OsTimerCallbackHandle not in use - bool callbackHandleFound = false; - uint32_t callbackHandleDescriptor = 0U; - for (auto& callbackHandle : OsTimer::s_callbackHandlePool) - { - if (!callbackHandle.m_inUse.load(std::memory_order_relaxed)) - { - std::lock_guard lock(callbackHandle.m_accessMutex); - // check in use again, just in case there we lost the race before we got the lock - if (callbackHandle.m_inUse.load(std::memory_order_relaxed)) - { - m_callbackHandleIndex++; - continue; - } - - callbackHandle.incrementDescriptor(); - callbackHandle.m_isTimerActive.store(true, std::memory_order_relaxed); - callbackHandle.m_inUse.store(true, std::memory_order_relaxed); - callbackHandle.m_timer = this; - - // it is sufficient to set the counter in the constructor - // (setting it in start leads to a subtle race in the loop of callbackHelper - // were they are checked should the callback call start) - callbackHandle.m_timerInvocationCounter.store(0U, std::memory_order_relaxed); - - callbackHandleFound = true; - callbackHandleDescriptor = callbackHandle.m_descriptor.load(std::memory_order_relaxed); - break; - } - m_callbackHandleIndex++; - } - - cxx::Ensures(callbackHandleFound && "Timerpool overflowed"); - - // Create the struct in order to configure the timer in the OS - struct sigevent asyncCallNotification = {}; - // We want the timer to call a function - asyncCallNotification.sigev_notify = SIGEV_THREAD; - // Set the function pointer to our sigevent - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) system struct - asyncCallNotification.sigev_notify_function = &callbackHelper; - // Save the pointer to self in order to execute the callback - asyncCallNotification.sigev_value.sival_ptr = nullptr; // initialize all bits of the sigval union for mem check - asyncCallNotification.sigev_value.sival_int = - Timer::OsTimerCallbackHandle::indexAndDescriptorToSigval(m_callbackHandleIndex, callbackHandleDescriptor) - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) system struct - .sival_int; - // Do not set any thread attributes - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) system struct - asyncCallNotification.sigev_notify_attributes = nullptr; - - posixCall(timer_create)(CLOCK_REALTIME, &asyncCallNotification, &m_timerId) - .failureReturnValue(-1) - .evaluate() - .and_then([this](auto&) { m_isInitialized = true; }) - .or_else([this](auto& r) { - m_isInitialized = false; - m_timerId = INVALID_TIMER_ID; - m_errorValue = createErrorFromErrno(r.errnum).value; - }); -} - -Timer::OsTimer::~OsTimer() noexcept -{ - if (m_timerId != INVALID_TIMER_ID) - { - stop().or_else([](auto) { std::cerr << "Unable to stop the timer in the destructor." << std::endl; }); - - // do not delete the timer while the callback is running, it could access the timer which is about to be deleted - auto& callbackHandle = OsTimer::s_callbackHandlePool[m_callbackHandleIndex]; - std::lock_guard lock(callbackHandle.m_accessMutex); - - posixCall(timer_delete)(m_timerId).failureReturnValue(-1).evaluate().or_else([this](auto& r) { - createErrorFromErrno(r.errnum); - std::cerr << "Unable to cleanup posix::Timer \"" << m_timerId << "\" in the destructor" << std::endl; - }); - - m_timerId = INVALID_TIMER_ID; - - callbackHandle.m_inUse.store(false, std::memory_order_seq_cst); - } -} - -void Timer::OsTimer::executeCallback() noexcept -{ - cxx::Expects(m_isInitialized && m_callback && "Timer fired but state is invalid"); - - m_callback(); -} - -cxx::expected Timer::OsTimer::start(const RunMode runMode, const CatchUpPolicy catchUpPolicy) noexcept -{ - // Convert units::Duration to itimerspec - struct itimerspec interval = {}; - interval.it_value = m_timeToWait.timespec(units::TimeSpecReference::None); - - if (runMode == RunMode::PERIODIC) - { - interval.it_interval = m_timeToWait.timespec(units::TimeSpecReference::None); - } - else - { - interval.it_interval.tv_sec = 0; - interval.it_interval.tv_nsec = 0; - } - - auto& handle = OsTimer::s_callbackHandlePool[m_callbackHandleIndex]; - // setting m_isTimerActive after timer_settime could lead to false negatives in a check which decides whether - // the callback should be executed, - // setting it beforehand leads to false positives which can be dealt with and avoid this problem - auto wasActive = handle.m_isTimerActive.exchange(true, std::memory_order_relaxed); - handle.m_catchUpPolicy = catchUpPolicy; - - // Set the timer - auto result = posixCall(timer_settime)(m_timerId, 0, &interval, nullptr).failureReturnValue(-1).evaluate(); - - if (result.has_error()) - { - // undo optimistically setting m_isTimerActive before - // not entirely safe against concurrent starts, we cannot detect these in this way - // needs extensive refactoring to achieve this (e.g. start and stop with mutex protection?) - handle.m_isTimerActive.exchange(wasActive, std::memory_order_relaxed); - return createErrorFromErrno(result.get_error().errnum); - } - - return cxx::success(); -} - -cxx::expected Timer::OsTimer::stop() noexcept -{ - auto& handle = OsTimer::s_callbackHandlePool[m_callbackHandleIndex]; - // Signal callbackHelper() that no callbacks shall be executed anymore - auto wasActive = handle.m_isTimerActive.exchange(false, std::memory_order_relaxed); - - if (!wasActive) - { - // Timer was not started yet - return cxx::success(); - } - - struct itimerspec interval = {}; - units::Duration zero = 0_s; - interval.it_value = zero.timespec(units::TimeSpecReference::None); - interval.it_interval.tv_sec = 0; - interval.it_interval.tv_nsec = 0; - - - // Disarm the timer - auto result = posixCall(timer_settime)(m_timerId, 0, &interval, nullptr).failureReturnValue(-1).evaluate(); - - if (result.has_error()) - { - return createErrorFromErrno(result.get_error().errnum); - } - - return cxx::success(); -} - -cxx::expected Timer::OsTimer::restart(const units::Duration timeToWait, - const RunMode runMode, - const CatchUpPolicy catchUpPolicy) noexcept -{ - // See if there is currently an active timer in the operating system and update m_isActive accordingly - auto gettimeResult = timeUntilExpiration(); - - if (gettimeResult.has_error()) - { - return cxx::error(gettimeResult.get_error()); - } - - // Set new timeToWait value - m_timeToWait = timeToWait; - - // Disarm running timer - if (OsTimer::s_callbackHandlePool[m_callbackHandleIndex].m_isTimerActive.load(std::memory_order_relaxed)) - { - auto stopResult = stop(); - - if (stopResult.has_error()) - { - return stopResult; - } - } - - // Activate the timer with the new timeToWait value - auto startResult = start(runMode, catchUpPolicy); - - if (startResult.has_error()) - { - return startResult; - } - return cxx::success(); -} - -cxx::expected Timer::OsTimer::timeUntilExpiration() noexcept -{ - struct itimerspec currentInterval = {}; - - auto result = posixCall(timer_gettime)(m_timerId, ¤tInterval).failureReturnValue(-1).evaluate(); - - if (result.has_error()) - { - return createErrorFromErrno(result.get_error().errnum); - } - - if (currentInterval.it_value.tv_sec == 0 && currentInterval.it_value.tv_nsec == 0) - { - // Timer is disarmed - OsTimer::s_callbackHandlePool[m_callbackHandleIndex].m_isTimerActive.store(false, std::memory_order_relaxed); - } - return cxx::success(currentInterval.it_value); -} - -cxx::expected Timer::OsTimer::getOverruns() noexcept -{ - auto result = posixCall(timer_getoverrun)(m_timerId).failureReturnValue(-1).evaluate(); - - if (result.has_error()) - { - return createErrorFromErrno(result.get_error().errnum); - } - return cxx::success(static_cast(result->value)); -} - -bool Timer::OsTimer::hasError() const noexcept -{ - return !m_isInitialized; -} - -TimerError Timer::OsTimer::getError() const noexcept -{ - return m_errorValue; -} - -cxx::expected Timer::now() noexcept -{ - struct timespec value = {}; - auto result = posixCall(clock_gettime)(CLOCK_REALTIME, &value).failureReturnValue(-1).evaluate(); - - if (result.has_error()) - { - return createErrorFromErrno(result.get_error().errnum); - } - - return cxx::success(value); -} - -Timer::Timer(const units::Duration timeToWait) noexcept - : m_timeToWait(timeToWait) - , m_creationTime(now().value()) -{ - if (m_timeToWait.toNanoseconds() == 0U) - { - m_errorValue = TimerError::TIMEOUT_IS_ZERO; - } -} - -Timer::Timer(const units::Duration timeToWait, const std::function& callback) noexcept - : m_timeToWait(timeToWait) - , m_creationTime(now().value()) -{ - if (m_timeToWait.toNanoseconds() == 0U) - { - m_errorValue = TimerError::TIMEOUT_IS_ZERO; - return; - } - - m_osTimer.emplace(timeToWait, callback); - if (m_osTimer->hasError()) - { - m_errorValue = m_osTimer->getError(); - m_osTimer.reset(); - } -} - -cxx::expected Timer::start(const RunMode runMode, const CatchUpPolicy catchUpPolicy) noexcept -{ - if (!m_osTimer.has_value()) - { - return cxx::error(TimerError::TIMER_NOT_INITIALIZED); - } - - return m_osTimer->start(runMode, catchUpPolicy); -} - -cxx::expected Timer::stop() noexcept -{ - if (!m_osTimer.has_value()) - { - return cxx::error(TimerError::TIMER_NOT_INITIALIZED); - } - - return m_osTimer->stop(); -} - -cxx::expected -Timer::restart(const units::Duration timeToWait, const RunMode runMode, const CatchUpPolicy catchUpPolicy) noexcept -{ - if (timeToWait.toNanoseconds() == 0U) - { - return cxx::error(TimerError::TIMEOUT_IS_ZERO); - } - - if (!m_osTimer.has_value()) - { - return cxx::error(TimerError::TIMER_NOT_INITIALIZED); - } - - return m_osTimer->restart(timeToWait, runMode, catchUpPolicy); -} - -cxx::expected Timer::timeUntilExpiration() noexcept -{ - if (!m_osTimer.has_value()) - { - return cxx::error(TimerError::TIMER_NOT_INITIALIZED); - } - - return m_osTimer->timeUntilExpiration(); -} - -cxx::expected Timer::getOverruns() noexcept -{ - if (!m_osTimer.has_value()) - { - return cxx::error(TimerError::TIMER_NOT_INITIALIZED); - } - - return m_osTimer->getOverruns(); -} - -bool Timer::hasError() const noexcept -{ - return m_errorValue != TimerError::NO_ERROR; -} - -TimerError Timer::getError() const noexcept -{ - return m_errorValue; -} - -cxx::error Timer::createErrorFromErrno(const int32_t errnum) noexcept -{ - TimerError timerError = TimerError::INTERNAL_LOGIC_ERROR; - switch (errnum) - { - case EAGAIN: - { - std::cerr << "Kernel failed to allocate timer structures" << std::endl; - timerError = TimerError::KERNEL_ALLOC_FAILED; - break; - } - case EINVAL: - { - std::cerr << "Provided invalid arguments for posix::Timer" << std::endl; - timerError = TimerError::INVALID_ARGUMENTS; - break; - } - case ENOMEM: - { - std::cerr << "Could not allocate memory for posix::Timer" << std::endl; - timerError = TimerError::ALLOC_MEM_FAILED; - break; - } - case EPERM: - { - std::cerr << "No permissions to set the clock" << std::endl; - timerError = TimerError::NO_PERMISSION; - break; - } - case EFAULT: - { - std::cerr << "An invalid pointer was provided" << std::endl; - timerError = TimerError::INVALID_POINTER; - break; - } - default: - { - std::cerr << "Internal logic error in posix::Timer occurred" << std::endl; - break; - } - } - return cxx::error(timerError); -} - -} // namespace posix -} // namespace iox diff --git a/iceoryx_hoofs/test/moduletests/test_posix_timer.cpp b/iceoryx_hoofs/test/moduletests/test_posix_timer.cpp deleted file mode 100644 index 7722e33670..0000000000 --- a/iceoryx_hoofs/test/moduletests/test_posix_timer.cpp +++ /dev/null @@ -1,505 +0,0 @@ -// Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. -// Copyright (c) 2021 - 2022 by Apex.AI Inc. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// SPDX-License-Identifier: Apache-2.0 - -#include "iceoryx_hoofs/cxx/expected.hpp" -#include "iceoryx_hoofs/internal/units/duration.hpp" -#include "iceoryx_hoofs/posix_wrapper/timer.hpp" -#include "iceoryx_hoofs/testing/test.hpp" -#include "iceoryx_hoofs/testing/timing_test.hpp" - -#include -#include -#include - -namespace -{ -using namespace ::testing; - -using namespace iox::units; -using namespace iox::units::duration_literals; - -using Timer = iox::posix::Timer; -using TimerError = iox::posix::TimerError; - -class Timer_test : public Test -{ - public: - virtual void SetUp() - { - numberOfCalls = 0; - } - - virtual void TearDown() - { - } - - Duration second{1_s}; - - std::atomic numberOfCalls{0}; - static const Duration TIMEOUT; -}; - -class TimerStopWatch_test : public Test -{ - public: - static const Duration TIMEOUT; -}; -const Duration TimerStopWatch_test::TIMEOUT{10_ms}; -const Duration Timer_test::TIMEOUT{TimerStopWatch_test::TIMEOUT}; - -TEST_F(TimerStopWatch_test, DurationOfZeroCausesError) -{ - ::testing::Test::RecordProperty("TEST_ID", "61067a67-7132-44e2-a99c-03ddb6ce963d"); - Timer sut(0_s); - EXPECT_THAT(sut.hasError(), Eq(true)); - EXPECT_THAT(sut.getError(), Eq(TimerError::TIMEOUT_IS_ZERO)); -} - -TEST_F(Timer_test, ZeroTimeoutIsNotAllowed) -{ - ::testing::Test::RecordProperty("TEST_ID", "e93d95af-1604-4652-a3fd-9602f47f7d6f"); - Timer sut(0_s, [] {}); - - EXPECT_THAT(sut.hasError(), Eq(true)); - EXPECT_THAT(sut.getError(), Eq(iox::posix::TimerError::TIMEOUT_IS_ZERO)); -} - -TIMING_TEST_F(Timer_test, CallbackNotExecutedWhenNotStarted, Repeat(5), [&] { - bool callbackExecuted{false}; - Timer sut(TIMEOUT, [&] { callbackExecuted = true; }); - - std::this_thread::sleep_for(std::chrono::milliseconds(4 * TIMEOUT.toMilliseconds() / 3)); - - TIMING_TEST_EXPECT_ALWAYS_FALSE(callbackExecuted); -}); - -TIMING_TEST_F(Timer_test, CallbackExecutedOnceAfterStart, Repeat(5), [&] { - std::atomic_int counter{0}; - Timer sut(1_ns, [&] { counter++; }); - ASSERT_FALSE(sut.start(Timer::RunMode::ONCE, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - - TIMING_TEST_EXPECT_TRUE(counter.load() == 1); -}); - -TIMING_TEST_F(Timer_test, CallbackExecutedPeriodicallyAfterStart, Repeat(5), [&] { - std::atomic_int counter{0}; - Timer sut(TIMEOUT, [&] { counter++; }); - ASSERT_FALSE(sut.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 10)); - auto finalCount = counter.load(); - - TIMING_TEST_EXPECT_TRUE(6 <= finalCount && finalCount <= 11); -}); - -TIMING_TEST_F(Timer_test, PeriodicCallbackNotExecutedPrematurely, Repeat(5), [&] { - std::atomic_int counter{0}; - Timer sut(TIMEOUT, [&] { counter++; }); - ASSERT_FALSE(sut.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(2 * TIMEOUT.toMilliseconds() / 3)); - TIMING_TEST_EXPECT_TRUE(counter.load() == 0); -}); - -TIMING_TEST_F(Timer_test, OneTimeCallbackNotExecutedPrematurely, Repeat(5), [&] { - std::atomic_int counter{0}; - Timer sut(TIMEOUT, [&] { counter++; }); - ASSERT_FALSE(sut.start(Timer::RunMode::ONCE, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(2 * TIMEOUT.toMilliseconds() / 3)); - TIMING_TEST_EXPECT_TRUE(counter.load() == 0); -}); - -TEST_F(Timer_test, StartFailsWhenNoCallbackIsSet) -{ - ::testing::Test::RecordProperty("TEST_ID", "a0029e9c-12e4-4bf6-a070-9c9afa5089cb"); - Timer sut(1_ms); - auto call = sut.start(Timer::RunMode::ONCE, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT); - - ASSERT_THAT(call.has_error(), Eq(true)); - EXPECT_THAT(call.get_error(), Eq(TimerError::TIMER_NOT_INITIALIZED)); -} - -TIMING_TEST_F(Timer_test, StartRunModeOnceIsStoppedAfterStop, Repeat(5), [&] { - std::atomic_int counter{0}; - Timer sut(TIMEOUT, [&] { counter++; }); - ASSERT_FALSE(sut.start(Timer::RunMode::ONCE, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - ASSERT_FALSE(sut.stop().has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(4 * TIMEOUT.toMilliseconds() / 3)); - - TIMING_TEST_EXPECT_TRUE(counter.load() == 0); -}); - -TIMING_TEST_F(Timer_test, StartRunPeriodicOnceIsStoppedAfterStop, Repeat(5), [&] { - std::atomic_int counter{0}; - Timer sut(TIMEOUT, [&] { counter++; }); - ASSERT_FALSE(sut.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - ASSERT_FALSE(sut.stop().has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(4 * TIMEOUT.toMilliseconds() / 3)); - - TIMING_TEST_EXPECT_TRUE(counter.load() == 0); -}); - -TIMING_TEST_F(Timer_test, StartRunPeriodicOnceIsStoppedInTheMiddleAfterStop, Repeat(5), [&] { - std::atomic_int counter{0}; - Timer sut(TIMEOUT, [&] { counter++; }); - ASSERT_FALSE(sut.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(4 * TIMEOUT.toMilliseconds() / 3)); - ASSERT_FALSE(sut.stop().has_error()); - auto previousCount = counter.load(); - std::this_thread::sleep_for(std::chrono::milliseconds(4 * TIMEOUT.toMilliseconds() / 3)); - - TIMING_TEST_EXPECT_TRUE(previousCount == counter.load()); -}); - -TEST_F(Timer_test, StopFailsWhenNoCallbackIsSet) -{ - ::testing::Test::RecordProperty("TEST_ID", "e1655ab0-7bf5-47bc-9991-8cd5ce3473c4"); - Timer sut(1_ms); - auto call = sut.stop(); - - ASSERT_THAT(call.has_error(), Eq(true)); - EXPECT_THAT(call.get_error(), Eq(TimerError::TIMER_NOT_INITIALIZED)); -} - -TIMING_TEST_F(Timer_test, RestartWithDifferentTiming, Repeat(5), [&] { - std::atomic_int counter{0}; - Timer sut(TIMEOUT * 10, [&] { counter++; }); - ASSERT_FALSE(sut.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(20 * TIMEOUT.toMilliseconds())); - ASSERT_FALSE(sut.restart(TIMEOUT, Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - counter = 0; - std::this_thread::sleep_for(std::chrono::milliseconds(10 * TIMEOUT.toMilliseconds())); - auto finalCount = counter.load(); - - TIMING_TEST_EXPECT_TRUE(6 <= finalCount && finalCount <= 13); -}); - -TIMING_TEST_F(Timer_test, RestartWithDifferentRunMode, Repeat(5), [&] { - std::atomic_int counter{0}; - Timer sut(TIMEOUT, [&] { counter++; }); - ASSERT_FALSE(sut.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(4 * TIMEOUT.toMilliseconds() / 3)); - ASSERT_FALSE(sut.restart(TIMEOUT, Timer::RunMode::ONCE, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - counter = 0; - - std::this_thread::sleep_for(std::chrono::milliseconds(2 * TIMEOUT.toMilliseconds() / 3)); - TIMING_TEST_EXPECT_TRUE(counter.load() == 0); - std::this_thread::sleep_for(std::chrono::milliseconds(2 * TIMEOUT.toMilliseconds() / 3)); - TIMING_TEST_EXPECT_TRUE(counter.load() == 1); - std::this_thread::sleep_for(std::chrono::milliseconds(2 * TIMEOUT.toMilliseconds() / 3)); - TIMING_TEST_EXPECT_TRUE(counter.load() == 1); -}); - -TIMING_TEST_F(Timer_test, RestartWithDifferentTimingAndRunMode, Repeat(5), [&] { - std::atomic_int counter{0}; - Timer sut(TIMEOUT * 2, [&] { counter++; }); - ASSERT_FALSE(sut.start(Timer::RunMode::ONCE, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(5 * TIMEOUT.toMilliseconds())); - counter = 0; - ASSERT_FALSE(sut.restart(TIMEOUT, Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - - std::this_thread::sleep_for(std::chrono::milliseconds(10 * TIMEOUT.toMilliseconds())); - - auto finalCount = counter.load(); - TIMING_TEST_EXPECT_TRUE(6 <= finalCount && finalCount <= 13); -}); - -TEST_F(Timer_test, RestartWithEmptyCallbackFails) -{ - ::testing::Test::RecordProperty("TEST_ID", "146acdfc-3d1c-44e8-88fd-6a476d657541"); - Timer sut(1_ms); - auto call = sut.restart(1_s, Timer::RunMode::ONCE, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT); - - ASSERT_THAT(call.has_error(), Eq(true)); - EXPECT_THAT(call.get_error(), Eq(TimerError::TIMER_NOT_INITIALIZED)); -} - -TEST_F(Timer_test, RestartWithTimeoutOfZeroFails) -{ - ::testing::Test::RecordProperty("TEST_ID", "3ecb4925-9b15-4eca-b3eb-6d325c336e46"); - Timer sut(1_ms, [] {}); - auto call = sut.restart(0_s, Timer::RunMode::ONCE, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT); - - ASSERT_THAT(call.has_error(), Eq(true)); - EXPECT_THAT(call.get_error(), Eq(TimerError::TIMEOUT_IS_ZERO)); -} - -TEST_F(Timer_test, TimeUntilExpirationFailsWithoutCallback) -{ - ::testing::Test::RecordProperty("TEST_ID", "8bdfd766-e223-4da2-9e3c-85de02534e86"); - Timer sut(1_ms); - auto call = sut.timeUntilExpiration(); - - ASSERT_THAT(call.has_error(), Eq(true)); - EXPECT_THAT(call.get_error(), Eq(TimerError::TIMER_NOT_INITIALIZED)); -} - -TIMING_TEST_F(Timer_test, TimeUntilExpirationWithCallback, Repeat(5), [&] { - Timer sut(TIMEOUT, [] {}); - ASSERT_FALSE(sut.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - auto timeUntilExpiration = sut.timeUntilExpiration().value().toMilliseconds(); - TIMING_TEST_EXPECT_TRUE(timeUntilExpiration > 2 * TIMEOUT.toMilliseconds() / 3); - - std::this_thread::sleep_for(std::chrono::milliseconds(2 * TIMEOUT.toMilliseconds() / 3)); - timeUntilExpiration = sut.timeUntilExpiration().value().toMilliseconds(); - TIMING_TEST_EXPECT_TRUE(1 <= timeUntilExpiration && timeUntilExpiration <= TIMEOUT.toMilliseconds() / 3); -}); - -TIMING_TEST_F(Timer_test, TimeUntilExpirationZeroAfterCallbackOnceCalled, Repeat(5), [&] { - Timer sut(TIMEOUT, [] {}); - ASSERT_FALSE(sut.start(Timer::RunMode::ONCE, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(10 * TIMEOUT.toMilliseconds())); - auto timeUntilExpiration = sut.timeUntilExpiration().value().toMilliseconds(); - TIMING_TEST_EXPECT_TRUE(timeUntilExpiration == 0); -}); - -TIMING_TEST_F(Timer_test, StoppingIsNonBlocking, Repeat(5), [&] { - Timer sut(1_ns, [] { std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 10)); }); - ASSERT_FALSE(sut.start(Timer::RunMode::ONCE, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - - auto startTime = std::chrono::system_clock::now(); - ASSERT_FALSE(sut.stop().has_error()); - auto endTime = std::chrono::system_clock::now(); - auto elapsedTime = std::chrono::duration_cast(endTime - startTime).count(); - - TIMING_TEST_EXPECT_TRUE(elapsedTime < 10); -}); - -TIMING_TEST_F(Timer_test, MultipleTimersRunningContinuously, Repeat(5), [&] { - struct TimeValPair - { - TimeValPair() - : timer(TIMEOUT, [&] { this->value++; }) - { - } - int value{0}; - Timer timer; - }; - - std::vector sutList(4); - - for (auto& sut : sutList) - { - ASSERT_FALSE(sut.timer.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - } - - constexpr int64_t REPETITIONS = 10; - std::this_thread::sleep_for(std::chrono::milliseconds(REPETITIONS * TIMEOUT.toMilliseconds())); - - for (auto& sut : sutList) - { - ASSERT_FALSE(sut.timer.stop().has_error()); - } - - std::this_thread::sleep_for(std::chrono::milliseconds(REPETITIONS * TIMEOUT.toMilliseconds())); - - for (auto& sut : sutList) - { - TIMING_TEST_EXPECT_TRUE(REPETITIONS / 2 <= sut.value && sut.value <= 3 * REPETITIONS / 2); - } -}); - -TIMING_TEST_F(Timer_test, MultipleTimersRunningOnce, Repeat(5), [&] { - struct TimeValPair - { - TimeValPair() - : timer(TIMEOUT, [&] { this->value++; }) - { - } - int value{0}; - Timer timer; - }; - - std::vector sutList(4); - - for (auto& sut : sutList) - { - ASSERT_FALSE(sut.timer.start(Timer::RunMode::ONCE, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - } - - std::this_thread::sleep_for(std::chrono::milliseconds(10 * TIMEOUT.toMilliseconds())); - - for (auto& sut : sutList) - { - TIMING_TEST_EXPECT_TRUE(sut.value == 1); - } -}); - -TIMING_TEST_F(Timer_test, DestructorIsBlocking, Repeat(5), [&] { - std::chrono::time_point startTime; - { - Timer sut(1_ns, [] { std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 10)); }); - ASSERT_FALSE(sut.start(Timer::RunMode::ONCE, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - startTime = std::chrono::system_clock::now(); - } - auto endTime = std::chrono::system_clock::now(); - auto elapsedTime = std::chrono::duration_cast(endTime - startTime).count(); - - TIMING_TEST_EXPECT_TRUE(10 <= elapsedTime); -}); - -TIMING_TEST_F(Timer_test, StartStopAndStartAgainIsNonBlocking, Repeat(5), [&] { - Timer sut(1_ns, [] { std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 10)); }); - ASSERT_FALSE(sut.start(Timer::RunMode::ONCE, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - - auto startTime = std::chrono::system_clock::now(); - ASSERT_FALSE(sut.stop().has_error()); - ASSERT_FALSE(sut.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - auto endTime = std::chrono::system_clock::now(); - auto elapsedTime = std::chrono::duration_cast(endTime - startTime).count(); - - TIMING_TEST_EXPECT_TRUE(elapsedTime <= 1); -}); - -TEST_F(Timer_test, GetOverrunsFailsWithNoCallback) -{ - ::testing::Test::RecordProperty("TEST_ID", "5b8ee9db-9394-459a-b31d-64cd6d57dae8"); - Timer sut(1_ms); - auto call = sut.getOverruns(); - - ASSERT_THAT(call.has_error(), Eq(true)); - EXPECT_THAT(call.get_error(), Eq(TimerError::TIMER_NOT_INITIALIZED)); -} - -TIMING_TEST_F(Timer_test, CatchUpPolicySkipToNextBeatContinuesWhenCallbackIsLongerThenTriggerTime, Repeat(5), [&] { - Timer sut(TIMEOUT, [] { std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 10)); }); - ASSERT_FALSE(sut.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 10)); - // EXPECT_NO_DEATH - EXPECT_TRUE(true); -}); - -TIMING_TEST_F(Timer_test, CatchUpPolicyImmediateContinuesWhenCallbackIsLongerThenTriggerTime, Repeat(5), [&] { - Timer sut(TIMEOUT, [] { std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 10)); }); - ASSERT_FALSE(sut.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::IMMEDIATE).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 10)); - // EXPECT_NO_DEATH - EXPECT_TRUE(true); -}); - -TIMING_TEST_F(Timer_test, CatchUpPolicyTerminateTerminatesWhenCallbackIsLongerThenTriggerTime, Repeat(5), [&] { - EXPECT_DEATH( - { - Timer sut(TIMEOUT, - [] { std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 10)); }); - ASSERT_FALSE(sut.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::TERMINATE).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 10)); - }, - ".*"); -}); - -TIMING_TEST_F(Timer_test, CatchUpPolicyChangeToTerminateChangesBehaviorToTerminate, Repeat(5), [&] { - EXPECT_DEATH( - { - Timer sut(TIMEOUT, - [] { std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 10)); }); - - ASSERT_FALSE(sut.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 10)); - ASSERT_FALSE(sut.restart(TIMEOUT, Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::TERMINATE).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 10)); - }, - ".*"); -}); - -TIMING_TEST_F(Timer_test, CatchUpPolicySkipToNextBeatSkipsCallbackWhenStillRunning, Repeat(5), [&] { - std::atomic_int counter{0}; - Timer sut(TIMEOUT, [&] { - ++counter; - // wait slightly longer then the timeout so that the effect is better measurable - std::this_thread::sleep_for(std::chrono::microseconds(TIMEOUT.toMilliseconds() * 1100)); - }); - - ASSERT_FALSE(sut.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - - std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 100)); - // every second callback is skipped since the runtime is slightly longer therefore - // the counter must be in that range - TIMING_TEST_EXPECT_TRUE(40 <= counter && counter <= 70); -}); - -TIMING_TEST_F(Timer_test, CatchUpPolicyImmediateCallsCallbackImmediatelyAfterFinishing, Repeat(5), [&] { - std::atomic_int counter{0}; - Timer sut(TIMEOUT, [&] { - ++counter; - // wait slightly longer then the timeout so that the effect is better measurable - std::this_thread::sleep_for(std::chrono::microseconds(TIMEOUT.toMilliseconds() * 1100)); - }); - - ASSERT_FALSE(sut.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::IMMEDIATE).has_error()); - - std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 100)); - - // the asap timer should in theory call the callback 90 times since it is calling it right - // after the last one finished and one callback takes 1.1 ms and we run for 100ms. - TIMING_TEST_EXPECT_TRUE(70 < counter && counter <= 100); -}); - -TIMING_TEST_F(Timer_test, CatchUpPolicySkipToNextBeatCallsLessCallbacksThanASAPTimer, Repeat(5), [&] { - std::atomic_int counter{0}; - Timer sut(TIMEOUT, [&] { - ++counter; - // wait slightly longer then the timeout so that the effect is better measurable - std::this_thread::sleep_for(std::chrono::microseconds(TIMEOUT.toMilliseconds() * 1100)); - }); - - ASSERT_FALSE(sut.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::SKIP_TO_NEXT_BEAT).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 100)); - int softTimerCounter = counter.load(); - ASSERT_FALSE(sut.stop().has_error()); - - counter.store(0); - ASSERT_FALSE(sut.start(Timer::RunMode::PERIODIC, Timer::CatchUpPolicy::IMMEDIATE).has_error()); - std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT.toMilliseconds() * 100)); - int asapTimerCounter = counter.load(); - ASSERT_FALSE(sut.stop().has_error()); - - TIMING_TEST_EXPECT_TRUE(softTimerCounter < asapTimerCounter); -}); - -/// Unit tests which segfaults (issue #243). If the segfault is fixed this unit test has -/// to be adjusted but for the moment it seems that it causes the segfault -/// reliable. -TEST_F(Timer_test, DISABLED_SelfTriggeringTimerWorksAndDoesNotCauseSegFault) -{ - ::testing::Test::RecordProperty("TEST_ID", "9ac73c73-44f9-46c1-81d8-51f1dd2a203e"); - Duration selfTriggerTimeout = 1_ns; - int repetitions = 100; - std::atomic_int counter{0}; - { - Timer sut{selfTriggerTimeout, [&] { - /// this timing is set to provoke the segfault. if the timing is - /// decreased the segfault is more unlikely to occure but with a - /// value of 100 ms it always happens. see issue #243 - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - if (counter < repetitions) - { - EXPECT_FALSE( - sut.restart(selfTriggerTimeout, Timer::RunMode::ONCE, Timer::CatchUpPolicy::IMMEDIATE) - .has_error()); - } - ++counter; - }}; - ASSERT_FALSE(sut.start(Timer::RunMode::ONCE, Timer::CatchUpPolicy::IMMEDIATE).has_error()); - - /// this time seems to be sufficient to cause the segfault - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - } -} -} // namespace diff --git a/iceoryx_hoofs/testing/include/iceoryx_hoofs/testing/timing_test.hpp b/iceoryx_hoofs/testing/include/iceoryx_hoofs/testing/timing_test.hpp index 171d6565d4..2adb9994fc 100644 --- a/iceoryx_hoofs/testing/include/iceoryx_hoofs/testing/timing_test.hpp +++ b/iceoryx_hoofs/testing/include/iceoryx_hoofs/testing/timing_test.hpp @@ -22,13 +22,13 @@ #include #include -/// @brief This headers provides TIMING_TEST unit test infrastructure. The idea -/// is that a timing test is running multiple times and if in one of the -/// repetitions all results of the test are successful then the timing -/// test itself is successful. +/// @brief This header provides TIMING_TEST unit test infrastructure. The idea +/// is that a timing test is running multiple times and if in one of the +/// repetitions all results of the test are successful then the timing +/// test itself is successful. /// -/// You can get to know them in praxis in the test_posix_timer.cpp unit test -/// for instance. +/// You can get to know them in praxis in the test_cxx_deadline_timer.cpp +/// unit test for instance. /// /// @code /// class MyClass_test : public Test {};