diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 0a6f8cb6cc..00f9876bf1 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -191,6 +191,18 @@ jobs: uses: ./.github/actions/install-iceoryx-deps-and-clang - run: ./tools/ci/build-test-ubuntu.sh 32-bit-x86 + build-test-ubuntu-32-64-bit-mix-mode: + # prevent stuck jobs consuming runners for 6 hours + timeout-minutes: 60 + runs-on: ubuntu-latest + needs: pre-flight-check + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install iceoryx dependencies and clang-tidy + uses: ./.github/actions/install-iceoryx-deps-and-clang + - run: ./tools/ci/build-test-32-64-bit-mix-mode.sh + # Bazel sanity check build-test-ubuntu-bazel: # prevent stuck jobs consuming runners for 6 hours diff --git a/.gitignore b/.gitignore index 0f17803082..255a7b955e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build/ +build-*/ build_out_of_tree/ build_package/ install/ diff --git a/doc/website/advanced/iceoray-on-32-bit.md b/doc/website/advanced/iceoray-on-32-bit.md new file mode 100644 index 0000000000..e86f89a8ff --- /dev/null +++ b/doc/website/advanced/iceoray-on-32-bit.md @@ -0,0 +1,108 @@ +# Status + +iceoryx works on 32-bit hardware, but only as technology preview and is not meant for production. + +See also https://github.com/eclipse-iceoryx/iceoryx/issues/2301 for more details and the limitations sections in this document. + +# Dependencies + +For 32-bit support, the following packages need to be installed on ubuntu + +```bash +sudo dpkg --add-architecture i386 +sudo apt install libacl1-dev:i386 libc6-dev-i386 libc6-dev-i386-cross libstdc++6-i386-cross gcc-multilib g++-multilib +``` + +# iceoryx as 32-bit library + +## Build steps + +The simplest way to build iceoryx is via the `iceoryx_build_test.sh` script + +```bash +tools/iceoryx_build_test.sh release 32-bit-x86 +``` + +If the script cannot be used, this are the steps with `cmake` on x86 + +```bash +cmake -S iceoryx_meta -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-m32 -malign-double" -DCMAKE_CXX_FLAGS="-m32 -malign-double" +cmake --build build +``` + +The `-m32` flag tells GCC to build iceoryx as 32-bit library on a 64-bit system. +The `-malign-double` flag is required to have 64-bit atomics on an 8 byte boundary. +Furthermore, it is required for the 32-64 bit mix-mode to enforce the same data layout when 32-bit application communicate with 64-bit applications. + +## Limitations + +An internal data structure, the `UsedChunkList`, might be left in a corrupt state when an application terminates abnormally when writing to this data structure. +In order to detect torn-writes on 32-bit, the data structure needs to be refactored. + +# iceoryx for communication between 32-bit and 64-bit applications aka 32-64 bit mix-mode + +## Attention + +Mixing 32-bit and 64-bit applications in a shared-memory environment is a non-trivial endeavor. +Since the data structures are shared between applications with varying bitness, one has to take special care of the layout of the data structures shared between the applications. + +For example, the following struct has a size of 16 bytes and is aligned to 8 byte on common 64-bit architectures like x86-64. +But on common 32-bit architectures like x86, it has a size of 12 bytes and is aligned to 4 bytes. + +```cpp +struct Foo { + bool bar {false}; + uint64_t baz {0}; +}; +``` + +As long as the applications share the same bitness, there is no need for special consideration. +However, when connecting 32-bit and 64-bit applications via shared memory, both must adhere to a common memory layout. +If the layout differs, it can lead to unpredictable behavior and errors in the applications. + +The simplest way to fix this specific alignment issue, is to use the `-malign-double` flag, which enforces an 8 byte alignment boundary for 64-bit data types on 32-bit architectures. + +## Build steps + +Similar to the 32-bit build, the simplest way to build for the 32-64 bit mix-mode is the `iceoryx_build_test.sh` script + +```bash +tools/iceoryx_build_test.sh release examples 32-bit-x86 experimental-32-64-bit-mix-mode --build-dir build-32 +tools/iceoryx_build_test.sh release examples experimental-32-64-bit-mix-mode --build-dir build-64 +``` + +If the script cannot be used, this are the steps with `cmake` on x86 + +```bash +cmake -S iceoryx_meta -B build-32 -DCMAKE_BUILD_TYPE=Release -DEXAMPLES=ON -DCMAKE_C_FLAGS="-m32 -malign-double" -DCMAKE_CXX_FLAGS="-m32 -malign-double" -DIOX_EXPERIMENTAL_32_64_BIT_MIX_MODE=ON +cmake --build build-32 + +cmake -S iceoryx_meta -B build-64 -DCMAKE_BUILD_TYPE=Release -DEXAMPLES=ON -DIOX_EXPERIMENTAL_32_64_BIT_MIX_MODE=ON +cmake --build build-64 +``` + +## Running the examples + +You can now mix and match 32-bit and 64-bit applications + +```bash +# terminal 1 +build-32/iox-roudi + +# terminal 2 +build-64/iceoryx_examples/request_response/iox-cpp-request-response-listener-server + +# terminal 3 +build-32/iceoryx_examples/request_response/iox-cpp-request-response-waitset-client +``` + +## Limitations + +In addition to the limitations of the 32-bit iceoryx, the mix-mode needs to ensure that all the data structures in shared memory have the same layout. +While the `-malign-double` flag can be used for the iceoryx data types, it does not work for POSIX data structures like `sem_t`. +These data types also have a different size for 32-bit and 64-bit architecture and are used in iceoryx in the shared-memory, e.g. for the `WaitSet`. +In order to make the iceoryx applications interoperable between 32-bit and 64-bit, a spin lock and a spin semaphore is used for their POSIX counterparts. +This can increase the CPU load and also the latency. + +For a production environment, the spin semaphore and spin lock needs to be replaced by a `futex` on Linux and a `WaitOnAddress` call on Windows. +For other OSes, a proper solution is yet to be found. diff --git a/doc/website/getting-started/installation.md b/doc/website/getting-started/installation.md index cfc486a208..6431da0b1b 100644 --- a/doc/website/getting-started/installation.md +++ b/doc/website/getting-started/installation.md @@ -6,7 +6,7 @@ All iceoryx libraries are deployed as independent CMake packages. Posh is using ### Dependencies -- 64-bit hardware (e.g. x86_64 or aarch64; 32-bit hardware might work, but is not supported) +- 64-bit hardware (e.g. x86_64 or aarch64; 32-bit hardware works, but only as technology preview and not meant for production) - [CMake](https://cmake.org), 3.16 or later - One of the following compilers: - [GCC](https://gcc.gnu.org), 8.3 or later diff --git a/doc/website/release-notes/iceoryx-unreleased.md b/doc/website/release-notes/iceoryx-unreleased.md index 0c40df1da2..e568e1e911 100644 --- a/doc/website/release-notes/iceoryx-unreleased.md +++ b/doc/website/release-notes/iceoryx-unreleased.md @@ -9,6 +9,7 @@ - The minimal supported GCC compiler is now 8.3 - The required C++ standard is now C++17 - Experimental 32-bit support for all platforms supporting 64-bit atomic operations +- Experimental support for communication between 32-bit and 64-bit applications **Features:** @@ -726,7 +727,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/BUILD.bazel b/iceoryx_hoofs/BUILD.bazel index aaa397e4ff..2213a10803 100644 --- a/iceoryx_hoofs/BUILD.bazel +++ b/iceoryx_hoofs/BUILD.bazel @@ -34,6 +34,7 @@ cc_library( srcs = glob([ "cli/source/*.cpp", "concurrent/buffer/source/*.cpp", + "concurrent/sync/source/*.cpp", "design/source/*.cpp", "filesystem/source/*.cpp", "memory/source/*.cpp", diff --git a/iceoryx_hoofs/CMakeLists.txt b/iceoryx_hoofs/CMakeLists.txt index afdcf8cca3..7aa4487598 100644 --- a/iceoryx_hoofs/CMakeLists.txt +++ b/iceoryx_hoofs/CMakeLists.txt @@ -104,6 +104,8 @@ 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 + concurrent/sync/source/spin_semaphore.cpp filesystem/source/file_reader.cpp filesystem/source/filesystem.cpp memory/source/bump_allocator.cpp @@ -134,7 +136,7 @@ iox_add_library( posix/sync/source/named_semaphore.cpp posix/sync/source/signal_handler.cpp posix/sync/source/signal_watcher.cpp - posix/sync/source/semaphore_interface.cpp + posix/sync/source/semaphore_helper.cpp posix/sync/source/thread.cpp posix/sync/source/unnamed_semaphore.cpp posix/time/source/adaptive_wait.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..32c66d4f67 --- /dev/null +++ b/iceoryx_hoofs/concurrent/sync/include/iox/spin_lock.hpp @@ -0,0 +1,98 @@ +// 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 "iceoryx_platform/unistd.hpp" +#include "iox/atomic.hpp" +#include "iox/lock_interface.hpp" + +#include + +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 + const concurrent::Atomic m_recursive{false}; + concurrent::Atomic m_pid{0}; + concurrent::Atomic m_recursive_count{0}; + concurrent::Atomic m_tid{}; +}; + +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/include/iox/spin_semaphore.hpp b/iceoryx_hoofs/concurrent/sync/include/iox/spin_semaphore.hpp new file mode 100644 index 0000000000..134838731d --- /dev/null +++ b/iceoryx_hoofs/concurrent/sync/include/iox/spin_semaphore.hpp @@ -0,0 +1,86 @@ + +// 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_SEMAPHORE_HPP +#define IOX_HOOFS_CONCURRENT_SYNC_SPIN_SEMAPHORE_HPP + +#include "iox/atomic.hpp" +#include "iox/deadline_timer.hpp" +#include "iox/detail/adaptive_wait.hpp" +#include "iox/optional.hpp" +#include "iox/semaphore_interface.hpp" +#include "iox/spin_lock.hpp" + +namespace iox +{ +namespace concurrent +{ +class SpinSemaphoreBuilder; + +class SpinSemaphore : public detail::SemaphoreInterface +{ + public: + using Builder = SpinSemaphoreBuilder; + + SpinSemaphore(const SpinSemaphore&) = delete; + SpinSemaphore(SpinSemaphore&&) = delete; + SpinSemaphore& operator=(const SpinSemaphore&) = delete; + SpinSemaphore& operator=(SpinSemaphore&&) = delete; + + ~SpinSemaphore() noexcept; + + private: + friend class optional; + friend class detail::SemaphoreInterface; + + explicit SpinSemaphore(int32_t initial_value) noexcept; + + expected post_impl() noexcept; + + expected wait_impl() noexcept; + + expected try_wait_impl() noexcept; + + expected timed_wait_impl(const units::Duration& timeout) noexcept; + + private: + concurrent::Atomic m_count{0}; + concurrent::Atomic m_to_be_destroyed{false}; + optional m_spinlock; +}; + +class SpinSemaphoreBuilder +{ + /// @brief Set the initial value of the spin semaphore + IOX_BUILDER_PARAMETER(uint32_t, initialValue, 0U) + + /// @brief Set if the spin semaphore can be stored in the shared memory + /// for inter process usage + IOX_BUILDER_PARAMETER(bool, isInterProcessCapable, true) + + public: + /// @brief Create a spin semaphore + /// @param[in] uninitializedSemaphore since the semaphore is not movable the user has to provide + /// memory to store the semaphore into - packed in an optional + /// @return an error describing the failure or success + expected create(optional& uninitializedSemaphore) const 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..dfe8bb2172 --- /dev/null +++ b/iceoryx_hoofs/concurrent/sync/source/spin_lock.cpp @@ -0,0 +1,123 @@ +// 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 +{ + auto pid = getpid(); + auto tid = std::this_thread::get_id(); + + if (m_pid.load() == pid && m_tid.load() == tid) + { + if (m_recursive.load(std::memory_order_relaxed)) + { + m_recursive_count.fetch_add(1); + + 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_pid.store(pid); + m_tid.store(tid); + m_recursive_count.store(1); + + return ok(); +} + +expected SpinLock::unlock_impl() noexcept +{ + auto pid = getpid(); + auto tid = std::this_thread::get_id(); + + if (m_pid.load() != pid || m_tid.load() != tid) + { + return err(UnlockError::NOT_OWNED_BY_THREAD); + } + + if (m_recursive_count.load() == 0) + { + return err(UnlockError::NOT_LOCKED); + } + + auto old_recursive_count = m_recursive_count.fetch_sub(1); + if (old_recursive_count == 1) + { + m_pid.store(0); + m_tid.store(std::thread::id()); + m_lock_flag.clear(std::memory_order_release); + } + + return ok(); +} + +expected SpinLock::try_lock_impl() noexcept +{ + auto pid = getpid(); + auto tid = std::this_thread::get_id(); + + if (m_pid.load() == pid && m_tid.load() == tid) + { + if (m_recursive.load(std::memory_order_relaxed)) + { + m_recursive_count.fetch_add(1); + return ok(TryLock::LOCK_SUCCEEDED); + } + + return ok(TryLock::FAILED_TO_ACQUIRE_LOCK); + } + + if (!m_lock_flag.test_and_set(std::memory_order_acquire)) + { + m_pid.store(pid); + m_tid.store(tid); + m_recursive_count.store(1); + + return ok(TryLock::LOCK_SUCCEEDED); + } + return ok(TryLock::FAILED_TO_ACQUIRE_LOCK); +} + +} // namespace concurrent +} // namespace iox diff --git a/iceoryx_hoofs/concurrent/sync/source/spin_semaphore.cpp b/iceoryx_hoofs/concurrent/sync/source/spin_semaphore.cpp new file mode 100644 index 0000000000..b09905b1f2 --- /dev/null +++ b/iceoryx_hoofs/concurrent/sync/source/spin_semaphore.cpp @@ -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 + +#include "iox/spin_semaphore.hpp" +#include "iox/detail/adaptive_wait.hpp" + +namespace iox +{ +namespace concurrent +{ +expected +SpinSemaphoreBuilder::create(optional& uninitializedSemaphore) const noexcept +{ + if (m_initialValue > IOX_SEM_VALUE_MAX) + { + IOX_LOG(ERROR, + "The spin semaphore initial value of " << m_initialValue << " exceeds the maximum semaphore value " + << IOX_SEM_VALUE_MAX); + return err(SemaphoreError::SEMAPHORE_OVERFLOW); + } + + uninitializedSemaphore.emplace(static_cast(m_initialValue)); + return ok(); +} + +SpinSemaphore::SpinSemaphore(int32_t initial_value) noexcept + : m_count(initial_value) +{ + SpinLockBuilder() + .is_inter_process_capable(true) + .lock_behavior(LockBehavior::NORMAL) + .create(m_spinlock) + .expect("Failed to create Lock"); +} + +SpinSemaphore::~SpinSemaphore() noexcept +{ + m_to_be_destroyed = true; +} + +expected SpinSemaphore::post_impl() noexcept +{ + std::lock_guard lock(*m_spinlock); + + if (m_count.load(std::memory_order_relaxed) == IOX_SEM_VALUE_MAX) + { + return err(SemaphoreError::SEMAPHORE_OVERFLOW); + } + + ++m_count; + return ok(); +} + +expected SpinSemaphore::wait_impl() noexcept +{ + detail::adaptive_wait spinner; + spinner.wait_loop([this] { + auto wait_result = this->tryWait(); + return wait_result.has_value() && !wait_result.value(); + }); + return ok(); +} + +expected SpinSemaphore::try_wait_impl() noexcept +{ + std::lock_guard lock(*m_spinlock); + if (m_to_be_destroyed.load(std::memory_order_relaxed)) + { + return ok(true); + } + if (m_count.load(std::memory_order_relaxed) > 0) + { + --m_count; + return ok(true); + } + return ok(false); +} + +expected SpinSemaphore::timed_wait_impl(const units::Duration& timeout) noexcept +{ + iox::deadline_timer deadline_timer(timeout); + detail::adaptive_wait spinner; + + auto ret_val = SemaphoreWaitState::TIMEOUT; + spinner.wait_loop([this, &deadline_timer, &ret_val] { + auto wait_result = this->tryWait(); + + if (wait_result.has_value() && wait_result.value()) + { + ret_val = SemaphoreWaitState::NO_TIMEOUT; + return false; + } + return !deadline_timer.hasExpired(); + }); + + return ok(ret_val); +} +} // 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/detail/semaphore_interface.hpp b/iceoryx_hoofs/design/include/iox/semaphore_interface.hpp similarity index 79% rename from iceoryx_hoofs/posix/sync/include/iox/detail/semaphore_interface.hpp rename to iceoryx_hoofs/design/include/iox/semaphore_interface.hpp index 355d7fce08..aa634a7646 100644 --- a/iceoryx_hoofs/posix/sync/include/iox/detail/semaphore_interface.hpp +++ b/iceoryx_hoofs/design/include/iox/semaphore_interface.hpp @@ -1,4 +1,5 @@ // Copyright (c) 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. @@ -14,8 +15,8 @@ // // SPDX-License-Identifier: Apache-2.0 -#ifndef IOX_HOOFS_POSIX_SYNC_SEMAPHORE_INTERFACE_HPP -#define IOX_HOOFS_POSIX_SYNC_SEMAPHORE_INTERFACE_HPP +#ifndef IOX_HOOFS_DESIGN_SEMAPHORE_INTERFACE_HPP +#define IOX_HOOFS_DESIGN_SEMAPHORE_INTERFACE_HPP #include "iceoryx_platform/semaphore.hpp" #include "iox/duration.hpp" @@ -59,32 +60,41 @@ class SemaphoreInterface /// @brief Increments the semaphore by one /// @return Fails when the value of the semaphore overflows or when the /// semaphore was removed from outside the process - expected post() noexcept; + expected post() noexcept + { + return static_cast(this)->post_impl(); + } /// @brief Decrements the semaphore by one. When the semaphore value is zero /// it blocks until the semaphore value is greater zero /// @return Fails when semaphore was removed from outside the process - expected wait() noexcept; + expected wait() noexcept + { + return static_cast(this)->wait_impl(); + } /// @brief Tries to decrement the semaphore by one. When the semaphore value is zero /// it returns false otherwise it returns true and decrement the value by one. /// @return Fails when semaphore was removed from outside the process - expected tryWait() noexcept; + expected tryWait() noexcept + { + return static_cast(this)->try_wait_impl(); + } /// @brief Tries to decrement the semaphore by one. When the semaphore value is zero /// it waits until the timeout has passed. /// @return If during the timeout time the semaphore value increases to non zero /// it returns SemaphoreWaitState::NO_TIMEOUT and decreases the semaphore by one /// otherwise returns SemaphoreWaitState::TIMEOUT - expected timedWait(const units::Duration& timeout) noexcept; + expected timedWait(const units::Duration& timeout) noexcept + { + return static_cast(this)->timed_wait_impl(timeout); + } protected: SemaphoreInterface() noexcept = default; - - private: - iox_sem_t* getHandle() noexcept; }; } // namespace detail } // namespace iox -#endif // IOX_HOOFS_POSIX_SYNC_SEMAPHORE_INTERFACE_HPP +#endif // IOX_HOOFS_DESIGN_SEMAPHORE_INTERFACE_HPP diff --git a/iceoryx_hoofs/posix/sync/include/iox/detail/semaphore_helper.hpp b/iceoryx_hoofs/posix/sync/include/iox/detail/semaphore_helper.hpp new file mode 100644 index 0000000000..e7084ebc02 --- /dev/null +++ b/iceoryx_hoofs/posix/sync/include/iox/detail/semaphore_helper.hpp @@ -0,0 +1,55 @@ +// Copyright (c) 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. +// 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_SYNC_SEMAPHORE_HELPER_HPP +#define IOX_HOOFS_POSIX_SYNC_SEMAPHORE_HELPER_HPP + +#include "iceoryx_platform/semaphore.hpp" +#include "iox/duration.hpp" +#include "iox/expected.hpp" +#include "iox/semaphore_interface.hpp" + +namespace iox +{ +namespace detail +{ +/// @brief Increments the semaphore by one +/// @return Fails when the value of the semaphore overflows or when the +/// semaphore was removed from outside the process +expected sem_post(iox_sem_t* handle) noexcept; + +/// @brief Decrements the semaphore by one. When the semaphore value is zero +/// it blocks until the semaphore value is greater zero +/// @return Fails when semaphore was removed from outside the process +expected sem_wait(iox_sem_t* handle) noexcept; + +/// @brief Tries to decrement the semaphore by one. When the semaphore value is zero +/// it returns false otherwise it returns true and decrement the value by one. +/// @return Fails when semaphore was removed from outside the process +expected sem_try_wait(iox_sem_t* handle) noexcept; + +/// @brief Tries to decrement the semaphore by one. When the semaphore value is zero +/// it waits until the timeout has passed. +/// @return If during the timeout time the semaphore value increases to non zero +/// it returns SemaphoreWaitState::NO_TIMEOUT and decreases the semaphore by one +/// otherwise returns SemaphoreWaitState::TIMEOUT +expected sem_timed_wait(iox_sem_t* handle, const units::Duration& timeout) noexcept; + +} // namespace detail +} // namespace iox + +#endif // IOX_HOOFS_POSIX_SYNC_SEMAPHORE_HELPER_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/include/iox/named_semaphore.hpp b/iceoryx_hoofs/posix/sync/include/iox/named_semaphore.hpp index ce73d474c0..0e71345139 100644 --- a/iceoryx_hoofs/posix/sync/include/iox/named_semaphore.hpp +++ b/iceoryx_hoofs/posix/sync/include/iox/named_semaphore.hpp @@ -1,4 +1,5 @@ // Copyright (c) 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,18 +20,22 @@ #include "iceoryx_platform/platform_settings.hpp" #include "iox/builder.hpp" -#include "iox/detail/semaphore_interface.hpp" #include "iox/expected.hpp" #include "iox/filesystem.hpp" #include "iox/optional.hpp" +#include "iox/semaphore_interface.hpp" #include "iox/string.hpp" namespace iox { +class NamedSemaphoreBuilder; + /// @brief A named posix semaphore. class NamedSemaphore final : public detail::SemaphoreInterface { public: + using Builder = NamedSemaphoreBuilder; + static constexpr uint64_t LENGTH_OF_SEMAPHORE_SLASH_PREFIX = 1U; using Name_t = string; @@ -46,7 +51,11 @@ class NamedSemaphore final : public detail::SemaphoreInterface friend class detail::SemaphoreInterface; NamedSemaphore(iox_sem_t* handle, const Name_t& name, const bool hasOwnership) noexcept; - iox_sem_t* getHandle() noexcept; + + expected post_impl() noexcept; + expected wait_impl() noexcept; + expected try_wait_impl() noexcept; + expected timed_wait_impl(const units::Duration& timeout) noexcept; iox_sem_t* m_handle = nullptr; Name_t m_name; diff --git a/iceoryx_hoofs/posix/sync/include/iox/unnamed_semaphore.hpp b/iceoryx_hoofs/posix/sync/include/iox/unnamed_semaphore.hpp index c9898e8eca..1e419a4483 100644 --- a/iceoryx_hoofs/posix/sync/include/iox/unnamed_semaphore.hpp +++ b/iceoryx_hoofs/posix/sync/include/iox/unnamed_semaphore.hpp @@ -1,4 +1,5 @@ // Copyright (c) 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. @@ -18,12 +19,14 @@ #define IOX_HOOFS_POSIX_SYNC_UNNAMED_SEMAPHORE_HPP #include "iox/builder.hpp" -#include "iox/detail/semaphore_interface.hpp" #include "iox/expected.hpp" #include "iox/optional.hpp" +#include "iox/semaphore_interface.hpp" namespace iox { +class UnnamedSemaphoreBuilder; + /// @brief A unnamed posix semaphore. // NOLINTJUSTIFICATION m_handle is always initialized during create in the UnnamedSemaphoreBuilder // hence it is impossible to create a UnnamedSemaphore without an initialized @@ -32,6 +35,8 @@ namespace iox class UnnamedSemaphore final : public detail::SemaphoreInterface { public: + using Builder = UnnamedSemaphoreBuilder; + UnnamedSemaphore(const UnnamedSemaphore&) noexcept = delete; UnnamedSemaphore(UnnamedSemaphore&&) noexcept = delete; UnnamedSemaphore& operator=(const UnnamedSemaphore&) noexcept = delete; @@ -44,7 +49,11 @@ class UnnamedSemaphore final : public detail::SemaphoreInterface; UnnamedSemaphore() noexcept = default; - iox_sem_t* getHandle() noexcept; + + expected post_impl() noexcept; + expected wait_impl() noexcept; + expected try_wait_impl() noexcept; + expected timed_wait_impl(const units::Duration& timeout) noexcept; iox_sem_t m_handle; bool m_destroyHandle = true; 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/posix/sync/source/named_semaphore.cpp b/iceoryx_hoofs/posix/sync/source/named_semaphore.cpp index 01eb583db0..552df47414 100644 --- a/iceoryx_hoofs/posix/sync/source/named_semaphore.cpp +++ b/iceoryx_hoofs/posix/sync/source/named_semaphore.cpp @@ -1,4 +1,5 @@ // Copyright (c) 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. @@ -15,6 +16,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "iox/named_semaphore.hpp" +#include "iox/detail/semaphore_helper.hpp" #include "iox/logging.hpp" #include "iox/posix_call.hpp" @@ -239,8 +241,20 @@ NamedSemaphore::~NamedSemaphore() noexcept } } -iox_sem_t* NamedSemaphore::getHandle() noexcept +expected NamedSemaphore::post_impl() noexcept { - return m_handle; + return detail::sem_post(m_handle); +} +expected NamedSemaphore::wait_impl() noexcept +{ + return detail::sem_wait(m_handle); +} +expected NamedSemaphore::try_wait_impl() noexcept +{ + return detail::sem_try_wait(m_handle); +} +expected NamedSemaphore::timed_wait_impl(const units::Duration& timeout) noexcept +{ + return detail::sem_timed_wait(m_handle, timeout); } } // namespace iox diff --git a/iceoryx_hoofs/posix/sync/source/semaphore_interface.cpp b/iceoryx_hoofs/posix/sync/source/semaphore_helper.cpp similarity index 57% rename from iceoryx_hoofs/posix/sync/source/semaphore_interface.cpp rename to iceoryx_hoofs/posix/sync/source/semaphore_helper.cpp index 3d84d9ad78..a5c20ab5e1 100644 --- a/iceoryx_hoofs/posix/sync/source/semaphore_interface.cpp +++ b/iceoryx_hoofs/posix/sync/source/semaphore_helper.cpp @@ -1,4 +1,5 @@ // Copyright (c) 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. @@ -14,17 +15,15 @@ // // SPDX-License-Identifier: Apache-2.0 -#include "iox/detail/semaphore_interface.hpp" +#include "iox/detail/semaphore_helper.hpp" #include "iox/logging.hpp" -#include "iox/named_semaphore.hpp" #include "iox/posix_call.hpp" -#include "iox/unnamed_semaphore.hpp" namespace iox { namespace detail { -SemaphoreError errnoToEnum(const int32_t errnum) noexcept +SemaphoreError sem_errno_to_enum(const int32_t errnum) noexcept { switch (errnum) { @@ -44,70 +43,57 @@ SemaphoreError errnoToEnum(const int32_t errnum) noexcept return SemaphoreError::UNDEFINED; } -template -iox_sem_t* SemaphoreInterface::getHandle() noexcept +expected sem_post(iox_sem_t* handle) noexcept { - return static_cast(this)->getHandle(); -} - -template -expected SemaphoreInterface::post() noexcept -{ - auto result = IOX_POSIX_CALL(iox_sem_post)(getHandle()).failureReturnValue(-1).evaluate(); + auto result = IOX_POSIX_CALL(iox_sem_post)(handle).failureReturnValue(-1).evaluate(); if (result.has_error()) { - return err(errnoToEnum(result.error().errnum)); + return err(sem_errno_to_enum(result.error().errnum)); } return ok(); } -template -expected -SemaphoreInterface::timedWait(const units::Duration& timeout) noexcept +expected sem_timed_wait(iox_sem_t* handle, const units::Duration& timeout) noexcept { const timespec timeoutAsTimespec = timeout.timespec(units::TimeSpecReference::Epoch); - auto result = IOX_POSIX_CALL(iox_sem_timedwait)(getHandle(), &timeoutAsTimespec) + auto result = IOX_POSIX_CALL(iox_sem_timedwait)(handle, &timeoutAsTimespec) .failureReturnValue(-1) .ignoreErrnos(ETIMEDOUT) .evaluate(); if (result.has_error()) { - return err(errnoToEnum(result.error().errnum)); + return err(sem_errno_to_enum(result.error().errnum)); } return ok((result.value().errnum == ETIMEDOUT) ? SemaphoreWaitState::TIMEOUT : SemaphoreWaitState::NO_TIMEOUT); } -template -expected SemaphoreInterface::tryWait() noexcept +expected sem_try_wait(iox_sem_t* handle) noexcept { - auto result = IOX_POSIX_CALL(iox_sem_trywait)(getHandle()).failureReturnValue(-1).ignoreErrnos(EAGAIN).evaluate(); + auto result = IOX_POSIX_CALL(iox_sem_trywait)(handle).failureReturnValue(-1).ignoreErrnos(EAGAIN).evaluate(); if (result.has_error()) { - return err(errnoToEnum(result.error().errnum)); + return err(sem_errno_to_enum(result.error().errnum)); } return ok(result.value().errnum != EAGAIN); } -template -expected SemaphoreInterface::wait() noexcept +expected sem_wait(iox_sem_t* handle) noexcept { - auto result = IOX_POSIX_CALL(iox_sem_wait)(getHandle()).failureReturnValue(-1).evaluate(); + auto result = IOX_POSIX_CALL(iox_sem_wait)(handle).failureReturnValue(-1).evaluate(); if (result.has_error()) { - return err(errnoToEnum(result.error().errnum)); + return err(sem_errno_to_enum(result.error().errnum)); } return ok(); } -template class SemaphoreInterface; -template class SemaphoreInterface; } // namespace detail } // namespace iox diff --git a/iceoryx_hoofs/posix/sync/source/unnamed_semaphore.cpp b/iceoryx_hoofs/posix/sync/source/unnamed_semaphore.cpp index 058ded3670..a32910beb2 100644 --- a/iceoryx_hoofs/posix/sync/source/unnamed_semaphore.cpp +++ b/iceoryx_hoofs/posix/sync/source/unnamed_semaphore.cpp @@ -1,4 +1,5 @@ // Copyright (c) 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. @@ -15,6 +16,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "iox/unnamed_semaphore.hpp" +#include "iox/detail/semaphore_helper.hpp" #include "iox/logging.hpp" #include "iox/posix_call.hpp" @@ -65,7 +67,7 @@ UnnamedSemaphore::~UnnamedSemaphore() noexcept { if (m_destroyHandle) { - auto result = IOX_POSIX_CALL(iox_sem_destroy)(getHandle()).failureReturnValue(-1).evaluate(); + auto result = IOX_POSIX_CALL(iox_sem_destroy)(&m_handle).failureReturnValue(-1).evaluate(); if (result.has_error()) { switch (result.error().errnum) @@ -81,8 +83,25 @@ UnnamedSemaphore::~UnnamedSemaphore() noexcept } } -iox_sem_t* UnnamedSemaphore::getHandle() noexcept + +expected UnnamedSemaphore::post_impl() noexcept +{ + return detail::sem_post(&m_handle); +} + +expected UnnamedSemaphore::wait_impl() noexcept { - return &m_handle; + return detail::sem_wait(&m_handle); } + +expected UnnamedSemaphore::try_wait_impl() noexcept +{ + return detail::sem_try_wait(&m_handle); +} + +expected UnnamedSemaphore::timed_wait_impl(const units::Duration& timeout) noexcept +{ + return detail::sem_timed_wait(&m_handle, timeout); +} + } // 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_hoofs/test/moduletests/test_posix_semaphore_interface.cpp b/iceoryx_hoofs/test/moduletests/test_posix_semaphore_interface.cpp index 8f0ae6b1a5..087413da8d 100644 --- a/iceoryx_hoofs/test/moduletests/test_posix_semaphore_interface.cpp +++ b/iceoryx_hoofs/test/moduletests/test_posix_semaphore_interface.cpp @@ -20,9 +20,10 @@ #include "iceoryx_hoofs/testing/watch_dog.hpp" #include "iceoryx_platform/platform_settings.hpp" #include "iceoryx_platform/time.hpp" -#include "iox/detail/semaphore_interface.hpp" #include "iox/duration.hpp" #include "iox/named_semaphore.hpp" +#include "iox/semaphore_interface.hpp" +#include "iox/spin_semaphore.hpp" #include "iox/unnamed_semaphore.hpp" #include "test.hpp" @@ -96,7 +97,16 @@ struct NamedSemaphoreTest } }; -using Implementations = Types; +struct SpinSemaphoreTest +{ + using SutType = iox::optional; + static iox::expected create(SutType& sut, const uint32_t initialValue) + { + return concurrent::SpinSemaphoreBuilder().initialValue(initialValue).isInterProcessCapable(false).create(sut); + } +}; + +using Implementations = Types; TYPED_TEST_SUITE(SemaphoreInterfaceTest, Implementations, ); diff --git a/iceoryx_meta/build_options.cmake b/iceoryx_meta/build_options.cmake index 8986a807b0..4a55062ebc 100644 --- a/iceoryx_meta/build_options.cmake +++ b/iceoryx_meta/build_options.cmake @@ -36,6 +36,7 @@ option(THREAD_SANITIZER "Build with thread sanitizer" OFF) option(TEST_WITH_ADDITIONAL_USER "Build Test with additional user accounts for testing access control" OFF) option(TEST_WITH_HUGE_PAYLOAD "Build Tests which use payload bigger than 2GB" OFF) option(TOML_CONFIG "TOML support for RouDi with dynamic configuration" ON) +option(IOX_EXPERIMENTAL_32_64_BIT_MIX_MODE "Enable experimental 32<->64 bit mix mode zero-copy communication" OFF) option(IOX_EXPERIMENTAL_POSH "Export experimental posh features (no guarantees)" OFF) option(IOX_REPRODUCIBLE_BUILD "Create reproducible builds by omit setting the build timestamp in the version header" ON) @@ -95,6 +96,7 @@ function(show_config_options) message(" TEST_WITH_ADDITIONAL_USER ...........: " ${TEST_WITH_ADDITIONAL_USER}) message(" TEST_WITH_HUGE_PAYLOAD ..............: " ${TEST_WITH_HUGE_PAYLOAD}) message(" TOML_CONFIG..........................: " ${TOML_CONFIG}) + message(" IOX_EXPERIMENTAL_32_64_BIT_MIX_MODE..: " ${IOX_EXPERIMENTAL_32_64_BIT_MIX_MODE}) message(" IOX_EXPERIMENTAL_POSH................: " ${IOX_EXPERIMENTAL_POSH}) message(" IOX_REPRODUCIBLE_BUILD...............: " ${IOX_REPRODUCIBLE_BUILD}) endfunction() diff --git a/iceoryx_posh/BUILD.bazel b/iceoryx_posh/BUILD.bazel index b72023af3e..a86d67e738 100644 --- a/iceoryx_posh/BUILD.bazel +++ b/iceoryx_posh/BUILD.bazel @@ -31,6 +31,8 @@ configure_file( "IOX_COMMUNICATION_POLICY": "ManyToManyPolicy", "IOX_DEFAULT_RESOURCE_PREFIX": "iox1", "IOX_EXPERIMENTAL_POSH_FLAG": "false", + "IOX_INTERPROCESS_LOCK": "mutex", + "IOX_INTERPROCESS_SEMAPHORE": "UnnamedSemaphore", "IOX_MAX_CHUNKS_ALLOCATED_PER_PUBLISHER_SIMULTANEOUSLY": "8", "IOX_MAX_CHUNKS_HELD_PER_SUBSCRIBER_SIMULTANEOUSLY": "256", "IOX_MAX_CLIENTS_PER_SERVER": "256", @@ -58,6 +60,8 @@ configure_file( "IOX_COMMUNICATION_POLICY": "ManyToManyPolicy", "IOX_DEFAULT_RESOURCE_PREFIX": "iox1", "IOX_EXPERIMENTAL_POSH_FLAG": "false", + "IOX_INTERPROCESS_LOCK": "mutex", + "IOX_INTERPROCESS_SEMAPHORE": "UnnamedSemaphore", "IOX_MAX_CHUNKS_ALLOCATED_PER_PUBLISHER_SIMULTANEOUSLY": "8", "IOX_MAX_CHUNKS_HELD_PER_SUBSCRIBER_SIMULTANEOUSLY": "256", "IOX_MAX_CLIENTS_PER_SERVER": "256", diff --git a/iceoryx_posh/cmake/IceoryxPoshDeployment.cmake b/iceoryx_posh/cmake/IceoryxPoshDeployment.cmake index 5ed93e67af..864c643fed 100644 --- a/iceoryx_posh/cmake/IceoryxPoshDeployment.cmake +++ b/iceoryx_posh/cmake/IceoryxPoshDeployment.cmake @@ -142,6 +142,15 @@ if(IOX_EXPERIMENTAL_POSH) else() set(IOX_EXPERIMENTAL_POSH_FLAG false) endif() + +if(IOX_EXPERIMENTAL_32_64_BIT_MIX_MODE) + set(IOX_INTERPROCESS_LOCK concurrent::SpinLock) + set(IOX_INTERPROCESS_SEMAPHORE concurrent::SpinSemaphore) +else() + set(IOX_INTERPROCESS_LOCK mutex) + set(IOX_INTERPROCESS_SEMAPHORE UnnamedSemaphore) +endif() + message(STATUS "[i] IOX_EXPERIMENTAL_POSH_FLAG: ${IOX_EXPERIMENTAL_POSH_FLAG}") message(STATUS "[i] <<<<<<<<<<<<<< End iceoryx_posh configuration: >>>>>>>>>>>>>>") diff --git a/iceoryx_posh/cmake/iceoryx_posh_deployment.hpp.in b/iceoryx_posh/cmake/iceoryx_posh_deployment.hpp.in index 71aa6126ca..ee0bd31e2d 100644 --- a/iceoryx_posh/cmake/iceoryx_posh_deployment.hpp.in +++ b/iceoryx_posh/cmake/iceoryx_posh_deployment.hpp.in @@ -21,6 +21,19 @@ namespace iox { +class mutex; +class UnnamedSemaphore; +namespace concurrent +{ +class SpinLock; +class SpinSemaphore; +} +namespace build +{ +using InterProcessLock = @IOX_INTERPROCESS_LOCK@; +using InterProcessSemaphore = @IOX_INTERPROCESS_SEMAPHORE@; +} + namespace popo { class SubscriberPortSingleProducer; @@ -46,28 +59,28 @@ constexpr uint32_t IOX_MAX_CHUNKS_ALLOCATED_PER_PUBLISHER_SIMULTANEOUSLY = constexpr uint64_t IOX_MAX_PUBLISHER_HISTORY = static_cast(@IOX_MAX_PUBLISHER_HISTORY@); constexpr uint32_t IOX_MAX_CHUNKS_HELD_PER_SUBSCRIBER_SIMULTANEOUSLY = static_cast(@IOX_MAX_CHUNKS_HELD_PER_SUBSCRIBER_SIMULTANEOUSLY@); - constexpr uint32_t IOX_MAX_NUMBER_OF_NOTIFIERS = static_cast(@IOX_MAX_NUMBER_OF_NOTIFIERS@); - constexpr uint32_t IOX_MAX_PROCESS_NUMBER = static_cast(@IOX_MAX_PROCESS_NUMBER@); - // NOTE: this is currently only used in the experimental API and corresponds to 'IOX_MAX_PROCESS_NUMBER' - // due to a limitation in the 'PointerRepository' - constexpr uint32_t IOX_MAX_NODE_NUMBER = static_cast(@IOX_MAX_NODE_NUMBER@); - // NOTE: this is currently set to 1 due to the limitation in the 'PointerRepository' - constexpr uint32_t IOX_MAX_NODE_PER_PROCESS = static_cast(@IOX_MAX_NODE_PER_PROCESS@); - constexpr uint32_t IOX_MAX_SHM_SEGMENTS = static_cast(@IOX_MAX_SHM_SEGMENTS@); - constexpr uint32_t IOX_MAX_NUMBER_OF_MEMPOOLS = static_cast(@IOX_MAX_NUMBER_OF_MEMPOOLS@); - constexpr uint32_t IOX_MAX_NUMBER_OF_CONDITION_VARIABLES = - static_cast(@IOX_MAX_NUMBER_OF_CONDITION_VARIABLES@); - constexpr uint32_t IOX_MAX_NODE_NAME_LENGTH = static_cast(@IOX_MAX_NODE_NAME_LENGTH@); - constexpr uint32_t IOX_MAX_ID_STRING_LENGTH = static_cast(@IOX_MAX_ID_STRING_LENGTH@); - constexpr uint32_t IOX_MAX_RUNTIME_NAME_LENGTH = static_cast(@IOX_MAX_RUNTIME_NAME_LENGTH@); - constexpr uint32_t IOX_MAX_RESPONSES_PROCESSED_SIMULTANEOUSLY = - static_cast(@IOX_MAX_RESPONSES_PROCESSED_SIMULTANEOUSLY@); - constexpr uint32_t IOX_MAX_RESPONSE_QUEUE_CAPACITY = static_cast(@IOX_MAX_RESPONSE_QUEUE_CAPACITY@); - constexpr uint32_t IOX_MAX_REQUEST_QUEUE_CAPACITY = static_cast(@IOX_MAX_REQUEST_QUEUE_CAPACITY@); - constexpr uint32_t IOX_MAX_CLIENTS_PER_SERVER = static_cast(@IOX_MAX_CLIENTS_PER_SERVER@); - constexpr uint32_t IOX_MAX_REQUESTS_PROCESSED_SIMULTANEOUSLY = static_cast(@IOX_MAX_REQUESTS_PROCESSED_SIMULTANEOUSLY@); - constexpr const char IOX_DEFAULT_RESOURCE_PREFIX[] = "@IOX_DEFAULT_RESOURCE_PREFIX@"; - constexpr bool IOX_EXPERIMENTAL_POSH_FLAG = @IOX_EXPERIMENTAL_POSH_FLAG@; +constexpr uint32_t IOX_MAX_NUMBER_OF_NOTIFIERS = static_cast(@IOX_MAX_NUMBER_OF_NOTIFIERS@); +constexpr uint32_t IOX_MAX_PROCESS_NUMBER = static_cast(@IOX_MAX_PROCESS_NUMBER@); +// NOTE: this is currently only used in the experimental API and corresponds to 'IOX_MAX_PROCESS_NUMBER' +// due to a limitation in the 'PointerRepository' +constexpr uint32_t IOX_MAX_NODE_NUMBER = static_cast(@IOX_MAX_NODE_NUMBER@); +// NOTE: this is currently set to 1 due to the limitation in the 'PointerRepository' +constexpr uint32_t IOX_MAX_NODE_PER_PROCESS = static_cast(@IOX_MAX_NODE_PER_PROCESS@); +constexpr uint32_t IOX_MAX_SHM_SEGMENTS = static_cast(@IOX_MAX_SHM_SEGMENTS@); +constexpr uint32_t IOX_MAX_NUMBER_OF_MEMPOOLS = static_cast(@IOX_MAX_NUMBER_OF_MEMPOOLS@); +constexpr uint32_t IOX_MAX_NUMBER_OF_CONDITION_VARIABLES = + static_cast(@IOX_MAX_NUMBER_OF_CONDITION_VARIABLES@); +constexpr uint32_t IOX_MAX_NODE_NAME_LENGTH = static_cast(@IOX_MAX_NODE_NAME_LENGTH@); +constexpr uint32_t IOX_MAX_ID_STRING_LENGTH = static_cast(@IOX_MAX_ID_STRING_LENGTH@); +constexpr uint32_t IOX_MAX_RUNTIME_NAME_LENGTH = static_cast(@IOX_MAX_RUNTIME_NAME_LENGTH@); +constexpr uint32_t IOX_MAX_RESPONSES_PROCESSED_SIMULTANEOUSLY = + static_cast(@IOX_MAX_RESPONSES_PROCESSED_SIMULTANEOUSLY@); +constexpr uint32_t IOX_MAX_RESPONSE_QUEUE_CAPACITY = static_cast(@IOX_MAX_RESPONSE_QUEUE_CAPACITY@); +constexpr uint32_t IOX_MAX_REQUEST_QUEUE_CAPACITY = static_cast(@IOX_MAX_REQUEST_QUEUE_CAPACITY@); +constexpr uint32_t IOX_MAX_CLIENTS_PER_SERVER = static_cast(@IOX_MAX_CLIENTS_PER_SERVER@); +constexpr uint32_t IOX_MAX_REQUESTS_PROCESSED_SIMULTANEOUSLY = static_cast(@IOX_MAX_REQUESTS_PROCESSED_SIMULTANEOUSLY@); +constexpr const char IOX_DEFAULT_RESOURCE_PREFIX[] = "@IOX_DEFAULT_RESOURCE_PREFIX@"; +constexpr bool IOX_EXPERIMENTAL_POSH_FLAG = @IOX_EXPERIMENTAL_POSH_FLAG@; // clang-format on } // namespace build } // namespace iox diff --git a/iceoryx_posh/include/iceoryx_posh/internal/popo/building_blocks/condition_variable_data.hpp b/iceoryx_posh/include/iceoryx_posh/internal/popo/building_blocks/condition_variable_data.hpp index 532539c19a..7406da1583 100644 --- a/iceoryx_posh/include/iceoryx_posh/internal/popo/building_blocks/condition_variable_data.hpp +++ b/iceoryx_posh/include/iceoryx_posh/internal/popo/building_blocks/condition_variable_data.hpp @@ -17,9 +17,11 @@ #ifndef IOX_POSH_POPO_BUILDING_BLOCKS_CONDITION_VARIABLE_DATA_HPP #define IOX_POSH_POPO_BUILDING_BLOCKS_CONDITION_VARIABLE_DATA_HPP +#include "iceoryx_posh/iceoryx_posh_deployment.hpp" #include "iceoryx_posh/iceoryx_posh_types.hpp" #include "iceoryx_posh/internal/posh_error_reporting.hpp" #include "iox/atomic.hpp" +#include "iox/spin_semaphore.hpp" #include "iox/unnamed_semaphore.hpp" namespace iox @@ -37,7 +39,7 @@ struct ConditionVariableData ConditionVariableData& operator=(ConditionVariableData&& rhs) = delete; ~ConditionVariableData() noexcept = default; - optional m_semaphore; + optional m_semaphore; RuntimeName_t m_runtimeName; concurrent::Atomic m_toBeDestroyed{false}; concurrent::Atomic m_activeNotifications[MAX_NUMBER_OF_NOTIFIERS]; 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..26881f0698 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 @@ -16,7 +16,9 @@ #ifndef IOX_POSH_POPO_BUILDING_BLOCKS_LOCKING_POLICY_HPP #define IOX_POSH_POPO_BUILDING_BLOCKS_LOCKING_POLICY_HPP +#include "iceoryx_posh/iceoryx_posh_deployment.hpp" #include "iox/mutex.hpp" +#include "iox/spin_lock.hpp" namespace iox { @@ -33,7 +35,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/condition_variable_data.cpp b/iceoryx_posh/source/popo/building_blocks/condition_variable_data.cpp index 9b786c5399..9755d803ec 100644 --- a/iceoryx_posh/source/popo/building_blocks/condition_variable_data.cpp +++ b/iceoryx_posh/source/popo/building_blocks/condition_variable_data.cpp @@ -30,9 +30,11 @@ ConditionVariableData::ConditionVariableData() noexcept ConditionVariableData::ConditionVariableData(const RuntimeName_t& runtimeName) noexcept : m_runtimeName(runtimeName) { - UnnamedSemaphoreBuilder().initialValue(0U).isInterProcessCapable(true).create(m_semaphore).or_else([](auto) { - IOX_REPORT_FATAL(PoshError::POPO__CONDITION_VARIABLE_DATA_FAILED_TO_CREATE_SEMAPHORE); - }); + build::InterProcessSemaphore::Builder() + .initialValue(0U) + .isInterProcessCapable(true) + .create(m_semaphore) + .or_else([](auto) { IOX_REPORT_FATAL(PoshError::POPO__CONDITION_VARIABLE_DATA_FAILED_TO_CREATE_SEMAPHORE); }); for (auto& id : m_activeNotifications) { diff --git a/iceoryx_posh/source/popo/building_blocks/locking_policy.cpp b/iceoryx_posh/source/popo/building_blocks/locking_policy.cpp index db1796b422..36066c4609 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) + build::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 diff --git a/tools/ci/build-test-32-64-bit-mix-mode.sh b/tools/ci/build-test-32-64-bit-mix-mode.sh new file mode 100755 index 0000000000..8149aa5087 --- /dev/null +++ b/tools/ci/build-test-32-64-bit-mix-mode.sh @@ -0,0 +1,149 @@ +#!/bin/bash +# 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 + +# This script builds iceoryx_hoofs und iceoryx_posh and executes all tests + +set -e + +COLOR_OFF='\033[0m' +COLOR_RED='\033[1;31m' +COLOR_GREEN='\033[1;32m' +COLOR_YELLOW='\033[1;33m' +COLOR_BLUE='\033[1;34m' + +WORKSPACE=$(git rev-parse --show-toplevel) +cd ${WORKSPACE} + +echo -e "${COLOR_BLUE}#${COLOR_OFF}" +echo -e "${COLOR_BLUE}# Building 32 bit binaries${COLOR_OFF}" +echo -e "${COLOR_BLUE}#${COLOR_OFF}" +tools/iceoryx_build_test.sh build-strict build-all examples experimental-32-64-bit-mix-mode 32-bit-x86 --build-dir build-32 + +echo -e "${COLOR_BLUE}#${COLOR_OFF}" +echo -e "${COLOR_BLUE}# Building 64 bit binaries${COLOR_OFF}" +echo -e "${COLOR_BLUE}#${COLOR_OFF}" +tools/iceoryx_build_test.sh build-strict build-all examples experimental-32-64-bit-mix-mode --build-dir build-64 + +echo -e "${COLOR_BLUE}#${COLOR_OFF}" +echo -e "${COLOR_BLUE}# Running 32 bit posh tests${COLOR_OFF}" +echo -e "${COLOR_BLUE}#${COLOR_OFF}" +build-32/posh/test/posh_moduletests +build-32/posh/test/posh_integrationtests + +echo -e "${COLOR_BLUE}#${COLOR_OFF}" +echo -e "${COLOR_BLUE}# Running 64 bit posh tests${COLOR_OFF}" +echo -e "${COLOR_BLUE}#${COLOR_OFF}" +build-64/posh/test/posh_moduletests +build-64/posh/test/posh_integrationtests + +ROUDI_APP=iox-roudi +ROUDI_CMD_32_BIT=build-32/${ROUDI_APP} +ROUDI_CMD_64_BIT=build-64/${ROUDI_APP} +ROUDI_LOG=/tmp/iceoryx-32-64-bit-test-roudi-log + +PUBLISHER_APP=iox-cpp-publisher-with-options +PUBLISHER_CMD_32_BIT=build-32/iceoryx_examples/iceoptions/${PUBLISHER_APP} +PUBLISHER_CMD_64_BIT=build-64/iceoryx_examples/iceoptions/${PUBLISHER_APP} +PUBLISHER_LOG=/tmp/iceoryx-32-64-bit-test-publisher-log + +SUBSCRIBER_APP=iox-cpp-subscriber-with-options +SUBSCRIBER_CMD_32_BIT=build-32/iceoryx_examples/iceoptions/${SUBSCRIBER_APP} +SUBSCRIBER_CMD_64_BIT=build-64/iceoryx_examples/iceoptions/${SUBSCRIBER_APP} +SUBSCRIBER_LOG=/tmp/iceoryx-32-64-bit-test-subscriber-log + +function print_log() +{ + echo -e "${COLOR_BLUE}### ${ROUDI_APP} log${COLOR_OFF}" + cat ${ROUDI_LOG} + echo -e "${COLOR_BLUE}### log end" + + echo -e "${COLOR_BLUE}### ${PUBLISHER_APP} log${COLOR_OFF}" + cat ${PUBLISHER_LOG} + echo -e "${COLOR_BLUE}### log end" + + echo -e "${COLOR_BLUE}### ${SUBSCRIBER_APP} log${COLOR_OFF}" + cat ${SUBSCRIBER_LOG} + echo -e "${COLOR_BLUE}### log end" +} + +function run_test() +{ + TEST_TIME_IN_SECONDS=10 + + ROUDI_CMD=$1 + PUBLISHER_CMD=$2 + SUBSCRIBER_CMD=$3 + + ${ROUDI_CMD} > ${ROUDI_LOG} & + ROUDI_PID=$! + + ${PUBLISHER_CMD} > ${PUBLISHER_LOG} & + PUBLISHER_PID=$! + + ${SUBSCRIBER_CMD} > ${SUBSCRIBER_LOG} & + SUBSCRIBER_PID=$! + + echo -e "${COLOR_YELLOW}Running applications for ${TEST_TIME_IN_SECONDS} seconds ... ${COLOR_OFF}" + sleep ${TEST_TIME_IN_SECONDS} + + kill ${SUBSCRIBER_PID} + kill ${PUBLISHER_PID} + kill ${ROUDI_PID} + wait ${ROUDI_PID} + + echo -e "${COLOR_BLUE}## Check publisher result${COLOR_OFF}" + EXPECTED_PUBLISHER_OUTPUT="sent value: 3" + if cat ${PUBLISHER_LOG} | grep --fixed-string --quiet "${EXPECTED_PUBLISHER_OUTPUT}"; then + echo -e "${COLOR_GREEN}Found '${EXPECTED_PUBLISHER_OUTPUT}' in publisher log!${COLOR_OFF}" + else + print_log + + echo -e "${COLOR_RED}Error! Could not find '${EXPECTED_PUBLISHER_OUTPUT}' in publisher log!${COLOR_OFF}" + exit 1 + fi + + echo -e "${COLOR_BLUE}## Check subscriber result${COLOR_OFF}" + EXPECTED_SUBSCRIBER_OUTPUT="got value: 3" + if cat ${SUBSCRIBER_LOG} | grep --fixed-string --quiet "${EXPECTED_SUBSCRIBER_OUTPUT}"; then + echo -e "${COLOR_GREEN}Found '${EXPECTED_SUBSCRIBER_OUTPUT}' in subscriber log!${COLOR_OFF}" + else + print_log + + echo -e "${COLOR_RED}Error! Could not find '${EXPECTED_SUBSCRIBER_OUTPUT}' in subscriber log!${COLOR_OFF}" + exit 1 + fi +} + +echo -e "${COLOR_BLUE}#${COLOR_OFF}" +echo -e "${COLOR_BLUE}# Running 32 bit iox-roudi with 32 bit publisher example and 64 bit subscriber example${COLOR_OFF}" +echo -e "${COLOR_BLUE}#${COLOR_OFF}" +run_test ${ROUDI_CMD_32_BIT} ${PUBLISHER_CMD_32_BIT} ${SUBSCRIBER_CMD_64_BIT} + +echo -e "${COLOR_BLUE}#${COLOR_OFF}" +echo -e "${COLOR_BLUE}# Running 32 bit iox-roudi with 64 bit publisher example and 32 bit subscriber example${COLOR_OFF}" +echo -e "${COLOR_BLUE}#${COLOR_OFF}" +run_test ${ROUDI_CMD_32_BIT} ${PUBLISHER_CMD_64_BIT} ${SUBSCRIBER_CMD_32_BIT} + +echo -e "${COLOR_BLUE}#${COLOR_OFF}" +echo -e "${COLOR_BLUE}# Running 64 bit iox-roudi with 32 bit publisher example and 64 bit subscriber example${COLOR_OFF}" +echo -e "${COLOR_BLUE}#${COLOR_OFF}" +run_test ${ROUDI_CMD_64_BIT} ${PUBLISHER_CMD_32_BIT} ${SUBSCRIBER_CMD_64_BIT} + +echo -e "${COLOR_BLUE}#${COLOR_OFF}" +echo -e "${COLOR_BLUE}# Running 64 bit iox-roudi with 64 bit publisher example and 32 bit subscriber example${COLOR_OFF}" +echo -e "${COLOR_BLUE}#${COLOR_OFF}" +run_test ${ROUDI_CMD_64_BIT} ${PUBLISHER_CMD_64_BIT} ${SUBSCRIBER_CMD_32_BIT} diff --git a/tools/iceoryx_build_test.sh b/tools/iceoryx_build_test.sh index b0e4ad278c..dc44fc5680 100755 --- a/tools/iceoryx_build_test.sh +++ b/tools/iceoryx_build_test.sh @@ -51,6 +51,7 @@ TEST_HUGE_PAYLOAD="OFF" OUT_OF_TREE_FLAG="OFF" EXAMPLE_FLAG="OFF" EXPERIMENTAL_FLAG="OFF" +EXPERIMENTAL_32_64_MIX_MODE_FLAG="OFF" BUILD_ALL_FLAG="OFF" BUILD_SHARED="OFF" TOML_FLAG="ON" @@ -170,6 +171,11 @@ while (( "$#" )); do EXPERIMENTAL_FLAG="ON" shift 1 ;; + "experimental-32-64-bit-mix-mode") + echo " [i] Build experimental 32<->64 bit mix mode zero-copy communication" + EXPERIMENTAL_32_64_MIX_MODE_FLAG="ON" + shift 1 + ;; "out-of-tree") echo " [i] Out-of-tree build" OUT_OF_TREE_FLAG="ON" @@ -253,6 +259,8 @@ while (( "$#" )); do echo " doc Build and generate doxygen" echo " help Print this help" echo " examples Build all examples" + echo " experimental Build experimental features" + echo " experimental-32-64-bit-mix-mode Enable experimental 32<->64 bit mix mode zero-copy communication" echo " one-to-many-only Restrict to 1:n communication only" echo " out-of-tree Out-of-tree build for CI" echo " package Create a debian package from clean build in build_package" @@ -331,6 +339,7 @@ if [ "$NO_BUILD" == false ]; then -DROUDI_ENVIRONMENT=$ROUDI_ENV_FLAG \ -DEXAMPLES=$EXAMPLE_FLAG \ -DIOX_EXPERIMENTAL_POSH=$EXPERIMENTAL_FLAG \ + -DIOX_EXPERIMENTAL_32_64_BIT_MIX_MODE=$EXPERIMENTAL_32_64_MIX_MODE_FLAG \ -DTOML_CONFIG=$TOML_FLAG \ -DBUILD_DOC=$BUILD_DOC \ -DBINDING_C=$BINDING_C_FLAG \