From 67f1481f33b10bc7cda289801f5d3d51209c8cec Mon Sep 17 00:00:00 2001 From: Mathias Kraus Date: Thu, 19 Sep 2024 23:05:01 +0200 Subject: [PATCH] iox-#2301 Add SpinLock --- .../release-notes/iceoryx-unreleased.md | 2 +- iceoryx_hoofs/CMakeLists.txt | 1 + .../concurrent/sync/include/iox/spin_lock.hpp | 93 ++++++++++ .../concurrent/sync/source/spin_lock.cpp | 128 +++++++++++++ .../design/include/iox/lock_interface.hpp | 112 ++++++++++++ .../posix/sync/include/iox/mutex.hpp | 142 +++++---------- iceoryx_hoofs/posix/sync/source/mutex.cpp | 101 +++++------ .../test/moduletests/test_posix_mutex.cpp | 169 +++++++++++------- .../popo/building_blocks/locking_policy.hpp | 9 +- .../popo/building_blocks/locking_policy.cpp | 16 +- 10 files changed, 549 insertions(+), 224 deletions(-) create mode 100644 iceoryx_hoofs/concurrent/sync/include/iox/spin_lock.hpp create mode 100644 iceoryx_hoofs/concurrent/sync/source/spin_lock.cpp create mode 100644 iceoryx_hoofs/design/include/iox/lock_interface.hpp diff --git a/doc/website/release-notes/iceoryx-unreleased.md b/doc/website/release-notes/iceoryx-unreleased.md index 0c40df1da2..3c0bcc59f5 100644 --- a/doc/website/release-notes/iceoryx-unreleased.md +++ b/doc/website/release-notes/iceoryx-unreleased.md @@ -726,7 +726,7 @@ // after iox::optional myMutex; iox::MutexBuilder() - .mutexType(iox::MutexType::RECURSIVE) + .lock_behavior(iox::LockBehavior::RECURSIVE) .create(myMutex); myMutex->lock(); ``` diff --git a/iceoryx_hoofs/CMakeLists.txt b/iceoryx_hoofs/CMakeLists.txt index afdcf8cca3..f35d8705fb 100644 --- a/iceoryx_hoofs/CMakeLists.txt +++ b/iceoryx_hoofs/CMakeLists.txt @@ -104,6 +104,7 @@ iox_add_library( cli/source/option_definition.cpp cli/source/option_manager.cpp concurrent/buffer/source/mpmc_loffli.cpp + concurrent/sync/source/spin_lock.cpp filesystem/source/file_reader.cpp filesystem/source/filesystem.cpp memory/source/bump_allocator.cpp diff --git a/iceoryx_hoofs/concurrent/sync/include/iox/spin_lock.hpp b/iceoryx_hoofs/concurrent/sync/include/iox/spin_lock.hpp new file mode 100644 index 0000000000..e7727c476d --- /dev/null +++ b/iceoryx_hoofs/concurrent/sync/include/iox/spin_lock.hpp @@ -0,0 +1,93 @@ +// Copyright (c) 2024 by ekxide IO GmbH. 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_CONCURRENT_SYNC_SPIN_LOCK_HPP +#define IOX_HOOFS_CONCURRENT_SYNC_SPIN_LOCK_HPP + +#include "iox/atomic.hpp" +#include "iox/lock_interface.hpp" + +namespace iox +{ +namespace concurrent +{ +class SpinLockBuilder; + +/// @brief A spin lock implementation as drop-in replacement for a mutex +class SpinLock : public LockInterface +{ + public: + using Builder = SpinLockBuilder; + + SpinLock(const SpinLock&) = delete; + SpinLock(SpinLock&&) = delete; + SpinLock& operator=(const SpinLock&) = delete; + SpinLock& operator=(SpinLock&&) = delete; + + ~SpinLock() noexcept = default; + + private: + friend class optional; + friend class LockInterface; + + explicit SpinLock(const LockBehavior lock_behavior) noexcept; + + expected lock_impl() noexcept; + + expected unlock_impl() noexcept; + + expected try_lock_impl() noexcept; + + struct LockInfo + { + pid_t tid; + uint32_t recursive_count; + }; + + private: + concurrent::AtomicFlag m_lock_flag = + ATOMIC_FLAG_INIT; // NOTE: only initialization via assignment is guaranteed to work + concurrent::Atomic m_lock_info{LockInfo{0, 0}}; + const concurrent::Atomic m_recursive{false}; +}; + +class SpinLockBuilder +{ + public: + enum class Error : uint8_t + { + LOCK_ALREADY_INITIALIZED, + INTER_PROCESS_LOCK_UNSUPPORTED_BY_PLATFORM, + UNKNOWN_ERROR + }; + + /// @brief Defines if the SpinLock should be usable in an inter process context. Default: true + IOX_BUILDER_PARAMETER(bool, is_inter_process_capable, true) + + /// @brief Sets the LockBehavior, default: LockBehavior::RECURSIVE + IOX_BUILDER_PARAMETER(LockBehavior, lock_behavior, LockBehavior::RECURSIVE) + + public: + /// @brief Initializes a provided uninitialized SpinLock + /// @param[in] uninitializedLock the uninitialized SpinLock which should be initialized + /// @return On failure LockCreationError which explains the error + expected create(optional& uninitializedLock) noexcept; +}; + +} // namespace concurrent +} // namespace iox + +#endif // IOX_HOOFS_CONCURRENT_SYNC_SPIN_LOCK_HPP diff --git a/iceoryx_hoofs/concurrent/sync/source/spin_lock.cpp b/iceoryx_hoofs/concurrent/sync/source/spin_lock.cpp new file mode 100644 index 0000000000..815a6371bb --- /dev/null +++ b/iceoryx_hoofs/concurrent/sync/source/spin_lock.cpp @@ -0,0 +1,128 @@ +// Copyright (c) 2024 by ekxide IO GmbH. 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 "iox/spin_lock.hpp" +#include "iox/detail/adaptive_wait.hpp" + +namespace iox +{ +namespace concurrent +{ +expected +SpinLockBuilder::create(optional& uninitializedLock) noexcept +{ + if (uninitializedLock.has_value()) + { + IOX_LOG(ERROR, "Unable to override an already initialized SpinLock with a new SpinLock"); + return err(Error::LOCK_ALREADY_INITIALIZED); + } + + uninitializedLock.emplace(m_lock_behavior); + return ok(); +} + +SpinLock::SpinLock(const LockBehavior lock_behavior) noexcept + : m_recursive(lock_behavior == LockBehavior::RECURSIVE) +{ +} + +expected SpinLock::lock_impl() noexcept +{ + pid_t tid = gettid(); + + auto lock_info = m_lock_info.load(); + + if (lock_info.tid == tid) + { + if (m_recursive.load(std::memory_order_relaxed)) + { + lock_info.recursive_count += 1; + m_lock_info.store(lock_info); + + return ok(); + } + + return err(LockError::DEADLOCK_CONDITION); + } + + detail::adaptive_wait spinner; + spinner.wait_loop([this] { return this->m_lock_flag.test_and_set(std::memory_order_acquire); }); + + m_lock_info.store(LockInfo{tid, 1}); + + return ok(); +} + +expected SpinLock::unlock_impl() noexcept +{ + pid_t tid = gettid(); + + auto lock_info = m_lock_info.load(); + + if (lock_info.tid != tid) + { + return err(UnlockError::NOT_OWNED_BY_THREAD); + } + + if (lock_info.recursive_count == 0) + { + return err(UnlockError::NOT_LOCKED); + } + + lock_info.recursive_count -= 1; + if (lock_info.recursive_count == 0) + { + lock_info.tid = 0; + m_lock_info.store(lock_info); + m_lock_flag.clear(std::memory_order_release); + } + else + { + m_lock_info.store(lock_info); + } + + return ok(); +} + +expected SpinLock::try_lock_impl() noexcept +{ + pid_t tid = gettid(); + + auto lock_info = m_lock_info.load(); + + if (lock_info.tid == tid) + { + if (m_recursive.load(std::memory_order_relaxed)) + { + lock_info.recursive_count += 1; + m_lock_info.store(lock_info); + return ok(TryLock::LOCK_SUCCEEDED); + } + + return ok(TryLock::FAILED_TO_ACQUIRE_LOCK); + } + + if (!m_lock_flag.test_and_set(std::memory_order_acquire)) + { + m_lock_info.store(LockInfo{tid, 1}); + + return ok(TryLock::LOCK_SUCCEEDED); + } + return ok(TryLock::FAILED_TO_ACQUIRE_LOCK); +} + +} // namespace concurrent +} // namespace iox diff --git a/iceoryx_hoofs/design/include/iox/lock_interface.hpp b/iceoryx_hoofs/design/include/iox/lock_interface.hpp new file mode 100644 index 0000000000..1a33137be7 --- /dev/null +++ b/iceoryx_hoofs/design/include/iox/lock_interface.hpp @@ -0,0 +1,112 @@ +// Copyright (c) 2024 by ekxide IO GmbH. 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_DESIGN_LOCK_INTERFACE_HPP +#define IOX_HOOFS_DESIGN_LOCK_INTERFACE_HPP + +#include "iceoryx_platform/pthread.hpp" +#include "iox/builder.hpp" +#include "iox/expected.hpp" +#include "iox/optional.hpp" + +#include + +namespace iox +{ +enum class LockError : uint8_t +{ + PRIORITY_MISMATCH, + MAXIMUM_NUMBER_OF_RECURSIVE_LOCKS_EXCEEDED, + DEADLOCK_CONDITION, + LOCK_ACQUIRED_BUT_HAS_INCONSISTENT_STATE_SINCE_OWNER_DIED, + UNKNOWN_ERROR +}; + +enum class UnlockError : uint8_t +{ + NOT_OWNED_BY_THREAD, + NOT_LOCKED, + UNKNOWN_ERROR +}; + +enum class TryLockError : uint8_t +{ + PRIORITY_MISMATCH, + MAXIMUM_NUMBER_OF_RECURSIVE_LOCKS_EXCEEDED, + LOCK_ACQUIRED_BUT_HAS_INCONSISTENT_STATE_SINCE_OWNER_DIED, + UNKNOWN_ERROR +}; + +enum class TryLock : uint8_t +{ + LOCK_SUCCEEDED, + FAILED_TO_ACQUIRE_LOCK +}; + +template +class LockInterface +{ + public: + /// @brief Engages the lock. + /// @return When it fails it returns an enum describing the error. + expected lock() noexcept + { + return static_cast(this)->lock_impl(); + } + + /// @brief Releases the lock. + /// @return When it fails it returns an enum describing the error. + expected unlock() noexcept + { + return static_cast(this)->unlock_impl(); + } + + /// @brief Tries to engage the lock. + /// @return If the lock was acquired LockInterfaceTryLock::LOCK_SUCCEEDED will be returned otherwise + /// LockInterfaceTryLock::FAILED_TO_ACQUIRE_LOCK. + /// If the lock is a recursive lock, this call will also succeed. + /// On failure it returns an enum describing the failure. + expected try_lock() noexcept + { + return static_cast(this)->try_lock_impl(); + } + + protected: + LockInterface() noexcept = default; +}; + +/// @brief Describes the behavior of the lock. +// NOLINTNEXTLINE(performance-enum-size) int32_t required for POSIX API +enum class LockBehavior : int32_t +{ + /// @brief Behavior without error detection and multiple locks from within + /// the same thread lead to deadlock + NORMAL = IOX_PTHREAD_MUTEX_NORMAL, + + /// @brief Multiple locks from within the same thread do not lead to deadlock + /// but one requires the same amount of unlocks to make the thread lockable + /// from other threads + RECURSIVE = IOX_PTHREAD_MUTEX_RECURSIVE, + + /// @brief Multiple locks from within the same thread will be detected and + /// reported. It detects also when unlock is called from a different + /// thread. + WITH_DEADLOCK_DETECTION = IOX_PTHREAD_MUTEX_ERRORCHECK, +}; + +} // namespace iox + +#endif // IOX_HOOFS_DESIGN_LOCK_INTERFACE_HPP diff --git a/iceoryx_hoofs/posix/sync/include/iox/mutex.hpp b/iceoryx_hoofs/posix/sync/include/iox/mutex.hpp index 367f2adccc..5de2bbc0d3 100644 --- a/iceoryx_hoofs/posix/sync/include/iox/mutex.hpp +++ b/iceoryx_hoofs/posix/sync/include/iox/mutex.hpp @@ -1,5 +1,6 @@ // Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. // Copyright (c) 2021 - 2022 by Apex.AI Inc. All rights reserved. +// Copyright (c) 2024 by ekxide IO GmbH. 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. @@ -19,53 +20,13 @@ #define IOX_HOOFS_POSIX_SYNC_MUTEX_HPP #include "iceoryx_platform/pthread.hpp" -#include "iox/builder.hpp" #include "iox/expected.hpp" +#include "iox/lock_interface.hpp" #include "iox/optional.hpp" namespace iox { -enum class MutexCreationError : uint8_t -{ - MUTEX_ALREADY_INITIALIZED, - INSUFFICIENT_MEMORY, - INSUFFICIENT_RESOURCES, - PERMISSION_DENIED, - INTER_PROCESS_MUTEX_UNSUPPORTED_BY_PLATFORM, - PRIORITIES_UNSUPPORTED_BY_PLATFORM, - USED_PRIORITY_UNSUPPORTED_BY_PLATFORM, - INVALID_PRIORITY_CEILING_VALUE, - UNKNOWN_ERROR -}; - -enum class MutexLockError : uint8_t -{ - PRIORITY_MISMATCH, - MAXIMUM_NUMBER_OF_RECURSIVE_LOCKS_EXCEEDED, - DEADLOCK_CONDITION, - LOCK_ACQUIRED_BUT_HAS_INCONSISTENT_STATE_SINCE_OWNER_DIED, - UNKNOWN_ERROR -}; - -enum class MutexUnlockError : uint8_t -{ - NOT_OWNED_BY_THREAD, - UNKNOWN_ERROR -}; - -enum class MutexTryLockError : uint8_t -{ - PRIORITY_MISMATCH, - MAXIMUM_NUMBER_OF_RECURSIVE_LOCKS_EXCEEDED, - LOCK_ACQUIRED_BUT_HAS_INCONSISTENT_STATE_SINCE_OWNER_DIED, - UNKNOWN_ERROR -}; - -enum class MutexTryLock : uint8_t -{ - LOCK_SUCCEEDED, - FAILED_TO_ACQUIRE_LOCK -}; +class MutexBuilder; /// @brief Wrapper for a inter-process pthread based mutex which does not use /// exceptions! @@ -74,10 +35,10 @@ enum class MutexTryLock : uint8_t /// /// int main() { /// optional myMutex; -/// iox::MutexBuilder().isInterProcessCapable(true) -/// .mutexType(MutexType::RECURSIVE) -/// .priorityInheritance(MutexPriorityInheritance::NONE) -/// .threadTerminationBehavior(MutexThreadTerminationBehavior::RELEASE_WHEN_LOCKED) +/// iox::MutexBuilder().is_inter_process_capable(true) +/// .lock_behavior(LockBehavior::RECURSIVE) +/// .priority_inheritance(LockPriorityInheritance::NONE) +/// .thread_termination_behavior(LockThreadTerminationBehavior::RELEASE_WHEN_LOCKED) /// .create(myMutex) /// .expect("Failed to create mutex!"); /// @@ -92,9 +53,11 @@ enum class MutexTryLock : uint8_t /// /// } /// @endcode -class mutex +class mutex : public LockInterface { public: + using Builder = MutexBuilder; + /// @brief Destroys the mutex. When the mutex is still locked this will fail and the /// mutex is leaked! If the MutexThreadTerminationBehavior is set to RELEASE_WHEN_LOCKED /// a locked mutex is unlocked and the handle is cleaned up correctly. @@ -109,24 +72,9 @@ class mutex mutex& operator=(const mutex&) = delete; mutex& operator=(mutex&&) = delete; - /// @brief Locks the mutex. - /// @return When it fails it returns an enum describing the error. - expected lock() noexcept; - - /// @brief Unlocks the mutex. - /// @return When it fails it returns an enum describing the error. - expected unlock() noexcept; - - /// @brief Tries to lock the mutex. - /// @return If the lock was acquired MutexTryLock::LOCK_SUCCEEDED will be returned otherwise - /// MutexTryLock::FAILED_TO_ACQUIRE_LOCK. - /// If the lock is of MutexType::RECURSIVE the lock will also succeed. - /// On failure it returns an enum describing the failure. - expected try_lock() noexcept; - /// @brief When a mutex owning thread/process with MutexThreadTerminationBehavior::RELEASE_WHEN_LOCKED dies then the /// next instance which would like to acquire the lock will get an - /// Mutex{Try}LockError::LOCK_ACQUIRED_BUT_HAS_INCONSISTENT_STATE_SINCE_OWNER_DIED error. This method puts + /// {Try}LockError::LOCK_ACQUIRED_BUT_HAS_INCONSISTENT_STATE_SINCE_OWNER_DIED error. This method puts /// the mutex again into a consistent state. If the mutex is already in a consistent state it will do /// nothing. void make_consistent() noexcept; @@ -134,8 +82,15 @@ class mutex private: mutex() noexcept = default; + expected lock_impl() noexcept; + + expected unlock_impl() noexcept; + + expected try_lock_impl() noexcept; + private: friend class MutexBuilder; + friend class LockInterface; friend class optional; iox_pthread_mutex_t m_handle = IOX_PTHREAD_MUTEX_INITIALIZER; @@ -143,25 +98,6 @@ class mutex bool m_hasInconsistentState = false; }; -/// @brief Describes the type of mutex. -// NOLINTNEXTLINE(performance-enum-size) int32_t required for POSIX API -enum class MutexType : int32_t -{ - /// @brief Behavior without error detection and multiple locks from within - /// the same thread lead to deadlock - NORMAL = IOX_PTHREAD_MUTEX_NORMAL, - - /// @brief Multiple locks from within the same thread do not lead to deadlock - /// but one requires the same amount of unlocks to make the thread lockable - /// from other threads - RECURSIVE = IOX_PTHREAD_MUTEX_RECURSIVE, - - /// @brief Multiple locks from within the same thread will be detected and - /// reported. It detects also when unlock is called from a different - /// thread. - WITH_DEADLOCK_DETECTION = IOX_PTHREAD_MUTEX_ERRORCHECK, -}; - /// @brief Describes how the priority of a mutex owning thread changes when another thread /// with an higher priority would like to acquire the mutex. // NOLINTNEXTLINE(performance-enum-size) int32_t required for POSIX API @@ -175,7 +111,7 @@ enum class MutexPriorityInheritance : int32_t INHERIT = IOX_PTHREAD_PRIO_INHERIT, /// @brief The priority of a thread holding the mutex is always promoted to the priority set up - /// in priorityCeiling. + /// in priority_ceiling. PROTECT = IOX_PTHREAD_PRIO_PROTECT }; @@ -187,7 +123,7 @@ enum class MutexThreadTerminationBehavior : int32_t /// This can also lead to a mutex leak in the destructor. STALL_WHEN_LOCKED = IOX_PTHREAD_MUTEX_STALLED, - /// @brief It implies the same behavior as MutexType::WITH_DEADLOCK_DETECTION. Additionally, when a mutex owning + /// @brief It implies the same behavior as LockBehavior::WITH_DEADLOCK_DETECTION. Additionally, when a mutex owning /// thread/process dies the mutex is put into an inconsistent state which can be recovered with /// Mutex::make_consistent(). The inconsistent state is detected by the next instance which calls /// Mutex::lock() or Mutex::try_lock() by the error value @@ -195,33 +131,49 @@ enum class MutexThreadTerminationBehavior : int32_t RELEASE_WHEN_LOCKED = IOX_PTHREAD_MUTEX_ROBUST, }; -/// @brief Builder which creates a posix mutex +/// @brief Builder which creates a mutex class MutexBuilder { + public: + enum class Error : uint8_t + { + LOCK_ALREADY_INITIALIZED, + INSUFFICIENT_MEMORY, + INSUFFICIENT_RESOURCES, + PERMISSION_DENIED, + INTER_PROCESS_LOCK_UNSUPPORTED_BY_PLATFORM, + PRIORITIES_UNSUPPORTED_BY_PLATFORM, + USED_PRIORITY_UNSUPPORTED_BY_PLATFORM, + INVALID_PRIORITY_CEILING_VALUE, + UNKNOWN_ERROR + }; + /// @brief Defines if the mutex should be usable in an inter process context. Default: true - IOX_BUILDER_PARAMETER(bool, isInterProcessCapable, true) + IOX_BUILDER_PARAMETER(bool, is_inter_process_capable, true) - /// @brief Sets the MutexType, default: MutexType::RECURSIVE - IOX_BUILDER_PARAMETER(MutexType, mutexType, MutexType::RECURSIVE) + /// @brief Sets the LockBehavior, default: LockBehavior::RECURSIVE + IOX_BUILDER_PARAMETER(LockBehavior, lock_behavior, LockBehavior::RECURSIVE) - /// @brief States how thread priority is adjusted when they own the mutex, default: MutexPriorityInheritance::NONE - IOX_BUILDER_PARAMETER(MutexPriorityInheritance, priorityInheritance, MutexPriorityInheritance::NONE) + /// @brief States how thread priority is adjusted when they own the mutex, default: + /// LockInterfacePriorityInheritance::NONE + IOX_BUILDER_PARAMETER(MutexPriorityInheritance, priority_inheritance, MutexPriorityInheritance::NONE) /// @brief Defines the maximum priority to which a thread which owns the thread can be promoted - IOX_BUILDER_PARAMETER(optional, priorityCeiling, nullopt) + IOX_BUILDER_PARAMETER(optional, priority_ceiling, nullopt) /// @brief Defines how a locked mutex behaves when the mutex owning thread terminates, /// default: MutexThreadTerminationBehavior::RELEASE_WHEN_LOCKED IOX_BUILDER_PARAMETER(MutexThreadTerminationBehavior, - threadTerminationBehavior, + thread_termination_behavior, MutexThreadTerminationBehavior::RELEASE_WHEN_LOCKED) public: /// @brief Initializes a provided uninitialized mutex - /// @param[in] uninitializedMutex the uninitialized mutex which should be initialized - /// @return On failure MutexError which explains the error - expected create(optional& uninitializedMutex) noexcept; + /// @param[in] uninitializedLock the uninitialized mutex which should be initialized + /// @return On failure LockCreationError which explains the error + expected create(optional& uninitializedMutex) noexcept; }; + } // namespace iox #endif // IOX_HOOFS_POSIX_SYNC_MUTEX_HPP diff --git a/iceoryx_hoofs/posix/sync/source/mutex.cpp b/iceoryx_hoofs/posix/sync/source/mutex.cpp index 17c4f6b294..f99e2ca75a 100644 --- a/iceoryx_hoofs/posix/sync/source/mutex.cpp +++ b/iceoryx_hoofs/posix/sync/source/mutex.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. // Copyright (c) 2021 - 2022 by Apex.AI Inc. All rights reserved. +// Copyright (c) 2024 by ekxide IO GmbH. 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. @@ -48,7 +49,7 @@ struct MutexAttributes } } - expected init() noexcept + expected init() noexcept { m_attributes.emplace(); auto result = IOX_POSIX_CALL(iox_pthread_mutexattr_init)(&*m_attributes).returnValueMatchesErrno().evaluate(); @@ -58,18 +59,18 @@ struct MutexAttributes { case ENOMEM: IOX_LOG(ERROR, "Not enough memory to initialize required mutex attributes"); - return err(MutexCreationError::INSUFFICIENT_MEMORY); + return err(MutexBuilder::Error::INSUFFICIENT_MEMORY); default: IOX_LOG(ERROR, "This should never happen. An unknown error occurred while initializing the mutex attributes."); - return err(MutexCreationError::UNKNOWN_ERROR); + return err(MutexBuilder::Error::UNKNOWN_ERROR); } } return ok(); } - expected enableIpcSupport(const bool enableIpcSupport) noexcept + expected enableIpcSupport(const bool enableIpcSupport) noexcept { auto result = IOX_POSIX_CALL(iox_pthread_mutexattr_setpshared)( @@ -83,33 +84,33 @@ struct MutexAttributes { case ENOTSUP: IOX_LOG(ERROR, "The platform does not support shared mutex (inter process mutex)"); - return err(MutexCreationError::INTER_PROCESS_MUTEX_UNSUPPORTED_BY_PLATFORM); + return err(MutexBuilder::Error::INTER_PROCESS_LOCK_UNSUPPORTED_BY_PLATFORM); default: IOX_LOG(ERROR, "This should never happen. An unknown error occurred while setting up the inter process " "configuration."); - return err(MutexCreationError::UNKNOWN_ERROR); + return err(MutexBuilder::Error::UNKNOWN_ERROR); } } return ok(); } - expected setType(const MutexType mutexType) noexcept + expected setType(const LockBehavior lock_behavior) noexcept { - auto result = IOX_POSIX_CALL(iox_pthread_mutexattr_settype)(&*m_attributes, static_cast(mutexType)) + auto result = IOX_POSIX_CALL(iox_pthread_mutexattr_settype)(&*m_attributes, static_cast(lock_behavior)) .returnValueMatchesErrno() .evaluate(); if (result.has_error()) { IOX_LOG(ERROR, "This should never happen. An unknown error occurred while setting up the mutex type."); - return err(MutexCreationError::UNKNOWN_ERROR); + return err(MutexBuilder::Error::UNKNOWN_ERROR); } return ok(); } - expected setProtocol(const MutexPriorityInheritance priorityInheritance) + expected setProtocol(const MutexPriorityInheritance priorityInheritance) { auto result = IOX_POSIX_CALL(iox_pthread_mutexattr_setprotocol)(&*m_attributes, static_cast(priorityInheritance)) @@ -121,24 +122,24 @@ struct MutexAttributes { case ENOSYS: IOX_LOG(ERROR, "The system does not support mutex priorities"); - return err(MutexCreationError::PRIORITIES_UNSUPPORTED_BY_PLATFORM); + return err(MutexBuilder::Error::PRIORITIES_UNSUPPORTED_BY_PLATFORM); case ENOTSUP: IOX_LOG(ERROR, "The used mutex priority is not supported by the platform"); - return err(MutexCreationError::USED_PRIORITY_UNSUPPORTED_BY_PLATFORM); + return err(MutexBuilder::Error::USED_PRIORITY_UNSUPPORTED_BY_PLATFORM); case EPERM: IOX_LOG(ERROR, "Insufficient permissions to set mutex priorities"); - return err(MutexCreationError::PERMISSION_DENIED); + return err(MutexBuilder::Error::PERMISSION_DENIED); default: IOX_LOG(ERROR, "This should never happen. An unknown error occurred while setting up the mutex priority."); - return err(MutexCreationError::UNKNOWN_ERROR); + return err(MutexBuilder::Error::UNKNOWN_ERROR); } } return ok(); } - expected setPrioCeiling(const int32_t priorityCeiling) noexcept + expected setPrioCeiling(const int32_t priorityCeiling) noexcept { auto result = IOX_POSIX_CALL(iox_pthread_mutexattr_setprioceiling)(&*m_attributes, static_cast(priorityCeiling)) @@ -150,10 +151,10 @@ struct MutexAttributes { case EPERM: IOX_LOG(ERROR, "Insufficient permissions to set the mutex priority ceiling."); - return err(MutexCreationError::PERMISSION_DENIED); + return err(MutexBuilder::Error::PERMISSION_DENIED); case ENOSYS: IOX_LOG(ERROR, "The platform does not support mutex priority ceiling."); - return err(MutexCreationError::PRIORITIES_UNSUPPORTED_BY_PLATFORM); + return err(MutexBuilder::Error::PRIORITIES_UNSUPPORTED_BY_PLATFORM); case EINVAL: { auto minimumPriority = detail::getSchedulerPriorityMinimum(detail::Scheduler::FIFO); @@ -163,20 +164,20 @@ struct MutexAttributes "The priority ceiling \"" << priorityCeiling << "\" is not in the valid priority range [ " << minimumPriority << ", " << maximumPriority << "] of the Scheduler::FIFO."); - return err(MutexCreationError::INVALID_PRIORITY_CEILING_VALUE); + return err(MutexBuilder::Error::INVALID_PRIORITY_CEILING_VALUE); } default: IOX_LOG( ERROR, "This should never happen. An unknown error occurred while setting up the mutex priority ceiling."); - return err(MutexCreationError::UNKNOWN_ERROR); + return err(MutexBuilder::Error::UNKNOWN_ERROR); } } return ok(); } - expected + expected setThreadTerminationBehavior(const MutexThreadTerminationBehavior behavior) noexcept { auto result = IOX_POSIX_CALL(iox_pthread_mutexattr_setrobust)(&*m_attributes, static_cast(behavior)) @@ -187,7 +188,7 @@ struct MutexAttributes IOX_LOG(ERROR, "This should never happen. An unknown error occurred while setting up the mutex thread " "termination behavior."); - return err(MutexCreationError::UNKNOWN_ERROR); + return err(MutexBuilder::Error::UNKNOWN_ERROR); } return ok(); @@ -196,8 +197,8 @@ struct MutexAttributes optional m_attributes; }; -expected initializeMutex(iox_pthread_mutex_t* const handle, - const iox_pthread_mutexattr_t* const attributes) noexcept +expected initializeMutex(iox_pthread_mutex_t* const handle, + const iox_pthread_mutexattr_t* const attributes) noexcept { auto initResult = IOX_POSIX_CALL(iox_pthread_mutex_init)(handle, attributes).returnValueMatchesErrno().evaluate(); if (initResult.has_error()) @@ -206,30 +207,30 @@ expected initializeMutex(iox_pthread_mutex_t* const ha { case EAGAIN: IOX_LOG(ERROR, "Not enough resources to initialize another mutex."); - return err(MutexCreationError::INSUFFICIENT_RESOURCES); + return err(MutexBuilder::Error::INSUFFICIENT_RESOURCES); case ENOMEM: IOX_LOG(ERROR, "Not enough memory to initialize mutex."); - return err(MutexCreationError::INSUFFICIENT_MEMORY); + return err(MutexBuilder::Error::INSUFFICIENT_MEMORY); case EPERM: IOX_LOG(ERROR, "Insufficient permissions to create mutex."); - return err(MutexCreationError::PERMISSION_DENIED); + return err(MutexBuilder::Error::PERMISSION_DENIED); default: IOX_LOG(ERROR, "This should never happen. An unknown error occurred while initializing the mutex handle. " "This is possible when the handle is an already initialized mutex handle."); - return err(MutexCreationError::UNKNOWN_ERROR); + return err(MutexBuilder::Error::UNKNOWN_ERROR); } } return ok(); } -expected MutexBuilder::create(optional& uninitializedMutex) noexcept +expected MutexBuilder::create(optional& uninitializedMutex) noexcept { if (uninitializedMutex.has_value()) { IOX_LOG(ERROR, "Unable to override an already initialized mutex with a new mutex"); - return err(MutexCreationError::MUTEX_ALREADY_INITIALIZED); + return err(Error::LOCK_ALREADY_INITIALIZED); } MutexAttributes mutexAttributes; @@ -240,34 +241,34 @@ expected MutexBuilder::create(optional& uniniti return result; } - result = mutexAttributes.enableIpcSupport(m_isInterProcessCapable); + result = mutexAttributes.enableIpcSupport(m_is_inter_process_capable); if (result.has_error()) { return result; } - result = mutexAttributes.setType(m_mutexType); + result = mutexAttributes.setType(m_lock_behavior); if (result.has_error()) { return result; } - result = mutexAttributes.setProtocol(m_priorityInheritance); + result = mutexAttributes.setProtocol(m_priority_inheritance); if (result.has_error()) { return result; } - if (m_priorityInheritance == MutexPriorityInheritance::PROTECT && m_priorityCeiling.has_value()) + if (m_priority_inheritance == MutexPriorityInheritance::PROTECT && m_priority_ceiling.has_value()) { - result = mutexAttributes.setPrioCeiling(*m_priorityCeiling); + result = mutexAttributes.setPrioCeiling(*m_priority_ceiling); if (result.has_error()) { return result; } } - result = mutexAttributes.setThreadTerminationBehavior(m_threadTerminationBehavior); + result = mutexAttributes.setThreadTerminationBehavior(m_thread_termination_behavior); if (result.has_error()) { return result; @@ -325,7 +326,7 @@ void mutex::make_consistent() noexcept } } -expected mutex::lock() noexcept +expected mutex::lock_impl() noexcept { auto result = IOX_POSIX_CALL(iox_pthread_mutex_lock)(&m_handle).returnValueMatchesErrno().evaluate(); if (result.has_error()) @@ -336,30 +337,30 @@ expected mutex::lock() noexcept IOX_LOG(ERROR, "The mutex has the attribute MutexPriorityInheritance::PROTECT set and the calling threads " "priority is greater than the mutex priority."); - return err(MutexLockError::PRIORITY_MISMATCH); + return err(LockError::PRIORITY_MISMATCH); case EAGAIN: IOX_LOG(ERROR, "Maximum number of recursive locks exceeded."); - return err(MutexLockError::MAXIMUM_NUMBER_OF_RECURSIVE_LOCKS_EXCEEDED); + return err(LockError::MAXIMUM_NUMBER_OF_RECURSIVE_LOCKS_EXCEEDED); case EDEADLK: IOX_LOG(ERROR, "Deadlock in mutex detected."); - return err(MutexLockError::DEADLOCK_CONDITION); + return err(LockError::DEADLOCK_CONDITION); case EOWNERDEAD: IOX_LOG(ERROR, "The thread/process which owned the mutex died. The mutex is now in an inconsistent state " "and must be put into a consistent state again with Mutex::make_consistent()"); this->m_hasInconsistentState = true; - return err(MutexLockError::LOCK_ACQUIRED_BUT_HAS_INCONSISTENT_STATE_SINCE_OWNER_DIED); + return err(LockError::LOCK_ACQUIRED_BUT_HAS_INCONSISTENT_STATE_SINCE_OWNER_DIED); default: IOX_LOG(ERROR, "This should never happen. An unknown error occurred while locking the mutex. " "This can indicate a either corrupted or non-POSIX compliant system."); - return err(MutexLockError::UNKNOWN_ERROR); + return err(LockError::UNKNOWN_ERROR); } } return ok(); } -expected mutex::unlock() noexcept +expected mutex::unlock_impl() noexcept { auto result = IOX_POSIX_CALL(iox_pthread_mutex_unlock)(&m_handle).returnValueMatchesErrno().evaluate(); if (result.has_error()) @@ -370,19 +371,19 @@ expected mutex::unlock() noexcept IOX_LOG(ERROR, "The mutex is not owned by the current thread. The mutex must be unlocked by the same " "thread it was locked by."); - return err(MutexUnlockError::NOT_OWNED_BY_THREAD); + return err(UnlockError::NOT_OWNED_BY_THREAD); default: IOX_LOG(ERROR, "This should never happen. An unknown error occurred while unlocking the mutex. " "This can indicate a either corrupted or non-POSIX compliant system."); - return err(MutexUnlockError::UNKNOWN_ERROR); + return err(UnlockError::UNKNOWN_ERROR); } } return ok(); } -expected mutex::try_lock() noexcept +expected mutex::try_lock_impl() noexcept { auto result = IOX_POSIX_CALL(iox_pthread_mutex_trylock)(&m_handle).returnValueMatchesErrno().ignoreErrnos(EBUSY).evaluate(); @@ -393,26 +394,26 @@ expected mutex::try_lock() noexcept { case EAGAIN: IOX_LOG(ERROR, "Maximum number of recursive locks exceeded."); - return err(MutexTryLockError::MAXIMUM_NUMBER_OF_RECURSIVE_LOCKS_EXCEEDED); + return err(TryLockError::MAXIMUM_NUMBER_OF_RECURSIVE_LOCKS_EXCEEDED); case EINVAL: IOX_LOG(ERROR, "The mutex has the attribute MutexPriorityInheritance::PROTECT set and the calling threads " "priority is greater than the mutex priority."); - return err(MutexTryLockError::PRIORITY_MISMATCH); + return err(TryLockError::PRIORITY_MISMATCH); case EOWNERDEAD: IOX_LOG(ERROR, "The thread/process which owned the mutex died. The mutex is now in an inconsistent state and must " "be put into a consistent state again with Mutex::make_consistent()"); this->m_hasInconsistentState = true; - return err(MutexTryLockError::LOCK_ACQUIRED_BUT_HAS_INCONSISTENT_STATE_SINCE_OWNER_DIED); + return err(TryLockError::LOCK_ACQUIRED_BUT_HAS_INCONSISTENT_STATE_SINCE_OWNER_DIED); default: IOX_LOG(ERROR, "This should never happen. An unknown error occurred while trying to lock the mutex. This can " "indicate a either corrupted or non-POSIX compliant system."); - return err(MutexTryLockError::UNKNOWN_ERROR); + return err(TryLockError::UNKNOWN_ERROR); } } - return (result->errnum == EBUSY) ? ok(MutexTryLock::FAILED_TO_ACQUIRE_LOCK) : ok(MutexTryLock::LOCK_SUCCEEDED); + return (result->errnum == EBUSY) ? ok(TryLock::FAILED_TO_ACQUIRE_LOCK) : ok(TryLock::LOCK_SUCCEEDED); } } // namespace iox diff --git a/iceoryx_hoofs/test/moduletests/test_posix_mutex.cpp b/iceoryx_hoofs/test/moduletests/test_posix_mutex.cpp index 5bc695d3e5..ca1b37706f 100644 --- a/iceoryx_hoofs/test/moduletests/test_posix_mutex.cpp +++ b/iceoryx_hoofs/test/moduletests/test_posix_mutex.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2019 by Robert Bosch GmbH. All rights reserved. -// Copyright (c) 2021 - 2022 by ApexAI Inc. All rights reserved. +// Copyright (c) 2021 - 2022 by Apex.AI Inc. All rights reserved. +// Copyright (c) 2024 by ekxide IO GmbH. 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. @@ -20,6 +21,7 @@ #include "iox/atomic.hpp" #include "iox/deadline_timer.hpp" #include "iox/mutex.hpp" +#include "iox/spin_lock.hpp" #include @@ -29,15 +31,19 @@ using namespace ::testing; using namespace iox; using namespace iox::units::duration_literals; +template class Mutex_test : public Test { public: + using SutType = SUT; + using SutTypeBuilder = typename SutType::Builder; + void SetUp() override { deadlockWatchdog.watchAndActOnFailure([] { std::terminate(); }); - ASSERT_FALSE(MutexBuilder().mutexType(MutexType::RECURSIVE).create(sutRecursive).has_error()); - ASSERT_FALSE(MutexBuilder().mutexType(MutexType::NORMAL).create(sutNonRecursive).has_error()); + ASSERT_FALSE(SutTypeBuilder().lock_behavior(LockBehavior::RECURSIVE).create(sutRecursive).has_error()); + ASSERT_FALSE(SutTypeBuilder().lock_behavior(LockBehavior::NORMAL).create(sutNonRecursive).has_error()); } void TearDown() override @@ -57,59 +63,58 @@ class Mutex_test : public Test } } - template - int64_t getDuration(const T& start, const T& end) - { - return std::chrono::duration_cast(end - start).count(); - } - iox::concurrent::Atomic doWaitForThread{true}; - iox::optional sutNonRecursive; - iox::optional sutRecursive; + iox::optional sutNonRecursive; + iox::optional sutRecursive; iox::units::Duration watchdogTimeout = 5_s; Watchdog deadlockWatchdog{watchdogTimeout}; }; -TEST_F(Mutex_test, TryLockAndUnlockWithNonRecursiveMutexWorks) +using Implementations = Types; + +TYPED_TEST_SUITE(Mutex_test, Implementations, ); + +TYPED_TEST(Mutex_test, TryLockAndUnlockWithNonRecursiveMutexWorks) { ::testing::Test::RecordProperty("TEST_ID", "4ed2c3f1-6c91-465e-a702-9ea25b5434bb"); - auto tryLockResult = sutNonRecursive->try_lock(); + auto tryLockResult = this->sutNonRecursive->try_lock(); ASSERT_FALSE(tryLockResult.has_error()); - EXPECT_THAT(*tryLockResult, Eq(MutexTryLock::LOCK_SUCCEEDED)); - EXPECT_FALSE(sutNonRecursive->unlock().has_error()); + EXPECT_THAT(*tryLockResult, Eq(TryLock::LOCK_SUCCEEDED)); + EXPECT_FALSE(this->sutNonRecursive->unlock().has_error()); } #ifndef _WIN32 -TEST_F(Mutex_test, TryLockWithNonRecursiveMutexReturnsFailsWhenLocked) +TYPED_TEST(Mutex_test, TryLockWithNonRecursiveMutexReturnsFailsWhenLocked) { ::testing::Test::RecordProperty("TEST_ID", "910b16e1-53ea-46c6-ad9a-9dcaa0bf7821"); - EXPECT_FALSE(sutNonRecursive->lock().has_error()); - auto tryLockResult = sutNonRecursive->try_lock(); + EXPECT_FALSE(this->sutNonRecursive->lock().has_error()); + auto tryLockResult = this->sutNonRecursive->try_lock(); ASSERT_FALSE(tryLockResult.has_error()); - EXPECT_THAT(*tryLockResult, Eq(MutexTryLock::FAILED_TO_ACQUIRE_LOCK)); - EXPECT_FALSE(sutNonRecursive->unlock().has_error()); + EXPECT_THAT(*tryLockResult, Eq(TryLock::FAILED_TO_ACQUIRE_LOCK)); + EXPECT_FALSE(this->sutNonRecursive->unlock().has_error()); } #endif -TEST_F(Mutex_test, LockAndUnlockWithNonRecursiveMutexWorks) +TYPED_TEST(Mutex_test, LockAndUnlockWithNonRecursiveMutexWorks) { ::testing::Test::RecordProperty("TEST_ID", "b83e4491-50cc-40ca-a6d0-5ad8baf346b9"); - EXPECT_FALSE(sutNonRecursive->lock().has_error()); - EXPECT_FALSE(sutNonRecursive->unlock().has_error()); + EXPECT_FALSE(this->sutNonRecursive->lock().has_error()); + EXPECT_FALSE(this->sutNonRecursive->unlock().has_error()); } -TEST_F(Mutex_test, RepeatedLockAndUnlockWithNonRecursiveMutexWorks) +TYPED_TEST(Mutex_test, RepeatedLockAndUnlockWithNonRecursiveMutexWorks) { ::testing::Test::RecordProperty("TEST_ID", "4c01c8cc-8cb2-4869-8ff3-c52e385a2289"); - EXPECT_FALSE(sutNonRecursive->lock().has_error()); - EXPECT_FALSE(sutNonRecursive->unlock().has_error()); - EXPECT_FALSE(sutNonRecursive->lock().has_error()); - EXPECT_FALSE(sutNonRecursive->unlock().has_error()); + EXPECT_FALSE(this->sutNonRecursive->lock().has_error()); + EXPECT_FALSE(this->sutNonRecursive->unlock().has_error()); + EXPECT_FALSE(this->sutNonRecursive->lock().has_error()); + EXPECT_FALSE(this->sutNonRecursive->unlock().has_error()); } -void tryLockReturnsFalseWhenMutexLockedInOtherThread(mutex& mutex) +template +void tryLockReturnsFalseWhenMutexLockedInOtherThread(SUT& mutex) { - iox::concurrent::Atomic tryLockState{MutexTryLock::LOCK_SUCCEEDED}; + iox::concurrent::Atomic tryLockState{TryLock::LOCK_SUCCEEDED}; ASSERT_FALSE(mutex.lock().has_error()); std::thread lockThread([&] { auto tryLockResult = mutex.try_lock(); @@ -118,24 +123,25 @@ void tryLockReturnsFalseWhenMutexLockedInOtherThread(mutex& mutex) }); lockThread.join(); - EXPECT_THAT(tryLockState.load(), Eq(MutexTryLock::FAILED_TO_ACQUIRE_LOCK)); + EXPECT_THAT(tryLockState.load(), Eq(TryLock::FAILED_TO_ACQUIRE_LOCK)); ASSERT_FALSE(mutex.unlock().has_error()); } -TEST_F(Mutex_test, TryLockReturnsFalseWhenMutexLockedInOtherThreadNonRecursiveMutex) +TYPED_TEST(Mutex_test, TryLockReturnsFalseWhenMutexLockedInOtherThreadNonRecursiveMutex) { ::testing::Test::RecordProperty("TEST_ID", "2bf2397b-e068-4883-870d-050d7338663f"); - tryLockReturnsFalseWhenMutexLockedInOtherThread(*sutNonRecursive); + tryLockReturnsFalseWhenMutexLockedInOtherThread(*this->sutNonRecursive); } -TEST_F(Mutex_test, TryLockReturnsFalseWhenMutexLockedInOtherThreadRecursiveMutex) +TYPED_TEST(Mutex_test, TryLockReturnsFalseWhenMutexLockedInOtherThreadRecursiveMutex) { ::testing::Test::RecordProperty("TEST_ID", "88f89346-dc69-491e-ad16-081dc29022b7"); - tryLockReturnsFalseWhenMutexLockedInOtherThread(*sutRecursive); + tryLockReturnsFalseWhenMutexLockedInOtherThread(*this->sutRecursive); } -void lockedMutexBlocks(Mutex_test* test, mutex& mutex) +template +void lockedMutexBlocks(Mutex_test* test, SUT& mutex) { const std::chrono::milliseconds WAIT_IN_MS(100); std::chrono::milliseconds blockingDuration{0}; @@ -165,64 +171,98 @@ void lockedMutexBlocks(Mutex_test* test, mutex& mutex) EXPECT_THAT(blockingDuration.count(), Ge(realWaitDuration.count())); } -TEST_F(Mutex_test, LockedMutexBlocksNonRecursiveMutex) +TYPED_TEST(Mutex_test, LockedMutexBlocksNonRecursiveMutex) { ::testing::Test::RecordProperty("TEST_ID", "de50bda2-c94e-413b-ab32-b255a04a8d8a"); - lockedMutexBlocks(this, *sutNonRecursive); + lockedMutexBlocks(this, *this->sutNonRecursive); } -TEST_F(Mutex_test, LockedMutexBlocksRecursiveMutex) +TYPED_TEST(Mutex_test, LockedMutexBlocksRecursiveMutex) { ::testing::Test::RecordProperty("TEST_ID", "59d4e6e0-d3c7-4d11-a131-01a2637883eb"); - lockedMutexBlocks(this, *sutRecursive); + lockedMutexBlocks(this, *this->sutRecursive); } #ifndef _WIN32 -TEST_F(Mutex_test, MutexWithDeadlockDetectionsFailsOnDeadlock) +TYPED_TEST(Mutex_test, MutexWithDeadlockDetectionsFailsOnDeadlock) { ::testing::Test::RecordProperty("TEST_ID", "feb07935-674d-4ebc-abaa-66664751719a"); - iox::optional sut; - ASSERT_FALSE(MutexBuilder().mutexType(MutexType::WITH_DEADLOCK_DETECTION).create(sut).has_error()); + using SutType = typename TestFixture::SutType; + using SutTypeBuilder = typename TestFixture::SutTypeBuilder; + iox::optional sut; + ASSERT_FALSE(SutTypeBuilder().lock_behavior(LockBehavior::WITH_DEADLOCK_DETECTION).create(sut).has_error()); EXPECT_FALSE(sut->lock().has_error()); auto result = sut->lock(); ASSERT_TRUE(result.has_error()); - EXPECT_THAT(result.error(), Eq(MutexLockError::DEADLOCK_CONDITION)); + EXPECT_THAT(result.error(), Eq(LockError::DEADLOCK_CONDITION)); EXPECT_FALSE(sut->unlock().has_error()); } #endif -TEST_F(Mutex_test, MutexWithDeadlockDetectionsFailsWhenSameThreadTriesToUnlockItTwice) +TYPED_TEST(Mutex_test, MutexWithDeadlockDetectionsFailsWhenSameThreadTriesToUnlockItTwice) { ::testing::Test::RecordProperty("TEST_ID", "062e411e-a5d3-4759-9faf-db6f4129d395"); - iox::optional sut; - ASSERT_FALSE(MutexBuilder().mutexType(MutexType::WITH_DEADLOCK_DETECTION).create(sut).has_error()); + using SutType = typename TestFixture::SutType; + using SutTypeBuilder = typename TestFixture::SutTypeBuilder; + iox::optional sut; + ASSERT_FALSE(SutTypeBuilder().lock_behavior(LockBehavior::WITH_DEADLOCK_DETECTION).create(sut).has_error()); EXPECT_FALSE(sut->lock().has_error()); EXPECT_FALSE(sut->unlock().has_error()); auto result = sut->unlock(); ASSERT_TRUE(result.has_error()); - EXPECT_THAT(result.error(), Eq(MutexUnlockError::NOT_OWNED_BY_THREAD)); + EXPECT_THAT(result.error(), Eq(UnlockError::NOT_OWNED_BY_THREAD)); } -TEST_F(Mutex_test, MutexWithDeadlockDetectionsFailsWhenAnotherThreadTriesToUnlock) +TYPED_TEST(Mutex_test, MutexWithDeadlockDetectionsFailsWhenAnotherThreadTriesToUnlock) { ::testing::Test::RecordProperty("TEST_ID", "4dcea981-2259-48c6-bf27-7839ad9013b4"); - iox::optional sut; - ASSERT_FALSE(MutexBuilder().mutexType(MutexType::WITH_DEADLOCK_DETECTION).create(sut).has_error()); + using SutType = typename TestFixture::SutType; + using SutTypeBuilder = typename TestFixture::SutTypeBuilder; + iox::optional sut; + ASSERT_FALSE(SutTypeBuilder().lock_behavior(LockBehavior::WITH_DEADLOCK_DETECTION).create(sut).has_error()); EXPECT_FALSE(sut->lock().has_error()); std::thread t([&] { auto result = sut->unlock(); ASSERT_TRUE(result.has_error()); - EXPECT_THAT(result.error(), Eq(MutexUnlockError::NOT_OWNED_BY_THREAD)); + EXPECT_THAT(result.error(), Eq(UnlockError::NOT_OWNED_BY_THREAD)); }); t.join(); EXPECT_FALSE(sut->unlock().has_error()); } +TYPED_TEST(Mutex_test, InitializingMutexTwiceResultsInError) +{ + ::testing::Test::RecordProperty("TEST_ID", "2f26c05f-08e5-481f-8a6e-2ceca3067cf0"); + using SutTypeBuilder = typename TestFixture::SutTypeBuilder; + auto result = SutTypeBuilder().create(this->sutRecursive); + + ASSERT_THAT(result.has_error(), Eq(true)); + EXPECT_THAT(result.error(), Eq(SutTypeBuilder::Error::LOCK_ALREADY_INITIALIZED)); +} + +class MutexThreadTermination_test : public Test +{ + public: + using SutType = mutex; + + void SetUp() override + { + deadlockWatchdog.watchAndActOnFailure([] { std::terminate(); }); + } + + void TearDown() override + { + } + + iox::units::Duration watchdogTimeout = 5_s; + Watchdog deadlockWatchdog{watchdogTimeout}; +}; + #if !defined(__APPLE__) && !defined(_WIN32) -TEST_F(Mutex_test, +TEST_F(MutexThreadTermination_test, MutexWithOnReleaseWhenLockedBehaviorUnlocksLockedMutexWhenThreadTerminatesAndSetsItIntoInconsistentState) { ::testing::Test::RecordProperty("TEST_ID", "4da7b1fb-23f1-421c-acf3-2a3d9e26b1a1"); @@ -230,8 +270,8 @@ TEST_F(Mutex_test, GTEST_SKIP() << "iox-#1683 QNX supports robust mutex not like the posix standard describes them."; #endif iox::optional sut; - ASSERT_FALSE(MutexBuilder() - .threadTerminationBehavior(MutexThreadTerminationBehavior::RELEASE_WHEN_LOCKED) + ASSERT_FALSE(SutType::Builder() + .thread_termination_behavior(MutexThreadTerminationBehavior::RELEASE_WHEN_LOCKED) .create(sut) .has_error()); @@ -240,21 +280,21 @@ TEST_F(Mutex_test, auto result = sut->try_lock(); ASSERT_TRUE(result.has_error()); - EXPECT_THAT(result.error(), MutexTryLockError::LOCK_ACQUIRED_BUT_HAS_INCONSISTENT_STATE_SINCE_OWNER_DIED); + EXPECT_THAT(result.error(), TryLockError::LOCK_ACQUIRED_BUT_HAS_INCONSISTENT_STATE_SINCE_OWNER_DIED); sut->make_consistent(); EXPECT_FALSE(sut->unlock().has_error()); } #if !defined(__FreeBSD__) -TEST_F(Mutex_test, MutexWithStallWhenLockedBehaviorDoesntUnlockMutexWhenThreadTerminates) +TEST_F(MutexThreadTermination_test, MutexWithStallWhenLockedBehaviorDoesntUnlockMutexWhenThreadTerminates) { ::testing::Test::RecordProperty("TEST_ID", "9beae890-f18e-4878-a957-312920eb1833"); #if defined(QNX) || defined(__QNX) || defined(__QNX__) || defined(QNX__) GTEST_SKIP() << "iox-#1683 QNX supports robust mutex not like the posix standard describes them."; #endif iox::optional sut; - ASSERT_FALSE(MutexBuilder() - .threadTerminationBehavior(MutexThreadTerminationBehavior::STALL_WHEN_LOCKED) + ASSERT_FALSE(SutType::Builder() + .thread_termination_behavior(MutexThreadTerminationBehavior::STALL_WHEN_LOCKED) .create(sut) .has_error()); @@ -263,17 +303,8 @@ TEST_F(Mutex_test, MutexWithStallWhenLockedBehaviorDoesntUnlockMutexWhenThreadTe auto result = sut->try_lock(); ASSERT_FALSE(result.has_error()); - EXPECT_THAT(*result, MutexTryLock::FAILED_TO_ACQUIRE_LOCK); + EXPECT_THAT(*result, TryLock::FAILED_TO_ACQUIRE_LOCK); } #endif #endif - -TEST_F(Mutex_test, InitializingMutexTwiceResultsInError) -{ - ::testing::Test::RecordProperty("TEST_ID", "2f26c05f-08e5-481f-8a6e-2ceca3067cf0"); - auto result = MutexBuilder().create(sutRecursive); - - ASSERT_THAT(result.has_error(), Eq(true)); - EXPECT_THAT(result.error(), Eq(MutexCreationError::MUTEX_ALREADY_INITIALIZED)); -} } // namespace diff --git a/iceoryx_posh/include/iceoryx_posh/internal/popo/building_blocks/locking_policy.hpp b/iceoryx_posh/include/iceoryx_posh/internal/popo/building_blocks/locking_policy.hpp index 973662da1a..7a56be3290 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/popo/building_blocks/locking_policy.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/popo/building_blocks/locking_policy.hpp @@ -17,11 +17,18 @@ #define IOX_POSH_POPO_BUILDING_BLOCKS_LOCKING_POLICY_HPP #include "iox/mutex.hpp" +#include "iox/spin_lock.hpp" namespace iox { namespace popo { +#ifdef IOX_EXPERIMENTAL_32_64_BIT_MIX_MODE +using InterProcessLock = concurrent::SpinLock; +#else +using InterProcessLock = mutex; +#endif + class ThreadSafePolicy { public: @@ -33,7 +40,7 @@ class ThreadSafePolicy bool tryLock() const noexcept; private: - mutable optional m_mutex; + mutable optional m_lock; }; class SingleThreadedPolicy diff --git a/iceoryx_posh/source/popo/building_blocks/locking_policy.cpp b/iceoryx_posh/source/popo/building_blocks/locking_policy.cpp index db1796b422..8d990c4ec3 100644 --- a/iceoryx_posh/source/popo/building_blocks/locking_policy.cpp +++ b/iceoryx_posh/source/popo/building_blocks/locking_policy.cpp @@ -25,16 +25,16 @@ namespace popo { ThreadSafePolicy::ThreadSafePolicy() noexcept { - MutexBuilder() - .isInterProcessCapable(true) - .mutexType(MutexType::RECURSIVE) - .create(m_mutex) + InterProcessLock::Builder() + .is_inter_process_capable(true) + .lock_behavior(LockBehavior::RECURSIVE) + .create(m_lock) .expect("Failed to create Mutex"); } void ThreadSafePolicy::lock() const noexcept { - if (!m_mutex->lock()) + if (!m_lock->lock()) { IOX_LOG(FATAL, "Locking of an inter-process mutex failed! This indicates that the application holding the lock " @@ -45,7 +45,7 @@ void ThreadSafePolicy::lock() const noexcept void ThreadSafePolicy::unlock() const noexcept { - if (!m_mutex->unlock()) + if (!m_lock->unlock()) { IOX_LOG(FATAL, "Unlocking of an inter-process mutex failed! This indicates that the resources were cleaned up " @@ -56,12 +56,12 @@ void ThreadSafePolicy::unlock() const noexcept bool ThreadSafePolicy::tryLock() const noexcept { - auto tryLockResult = m_mutex->try_lock(); + auto tryLockResult = m_lock->try_lock(); if (tryLockResult.has_error()) { IOX_REPORT_FATAL(PoshError::POPO__CHUNK_TRY_LOCK_ERROR); } - return *tryLockResult == MutexTryLock::LOCK_SUCCEEDED; + return *tryLockResult == TryLock::LOCK_SUCCEEDED; } void SingleThreadedPolicy::lock() const noexcept