Skip to content

Commit

Permalink
iox-#2301 Add SpinLock
Browse files Browse the repository at this point in the history
  • Loading branch information
elBoberido committed Sep 20, 2024
1 parent 5ea8482 commit c983dcc
Show file tree
Hide file tree
Showing 11 changed files with 542 additions and 224 deletions.
2 changes: 1 addition & 1 deletion doc/website/release-notes/iceoryx-unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@
// after
iox::optional<iox::mutex> myMutex;
iox::MutexBuilder()
.mutexType(iox::MutexType::RECURSIVE)
.lock_behavior(iox::LockBehavior::RECURSIVE)
.create(myMutex);
myMutex->lock();
```
Expand Down
1 change: 1 addition & 0 deletions iceoryx_hoofs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions iceoryx_hoofs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ iox_add_library(
cli/source/option_definition.cpp
cli/source/option_manager.cpp
concurrent/buffer/source/mpmc_loffli.cpp
concurrent/sync/source/spin_lock.cpp
filesystem/source/file_reader.cpp
filesystem/source/filesystem.cpp
memory/source/bump_allocator.cpp
Expand Down
96 changes: 96 additions & 0 deletions iceoryx_hoofs/concurrent/sync/include/iox/spin_lock.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) 2024 by ekxide IO GmbH. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

#ifndef IOX_HOOFS_CONCURRENT_SYNC_SPIN_LOCK_HPP
#define IOX_HOOFS_CONCURRENT_SYNC_SPIN_LOCK_HPP

#include "iox/atomic.hpp"
#include "iox/lock_interface.hpp"

#include <thread>

namespace iox
{
namespace concurrent
{
class SpinLockBuilder;

/// @brief A spin lock implementation as drop-in replacement for a mutex
class SpinLock : public LockInterface<SpinLock>
{
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<SpinLock>;
friend class LockInterface<SpinLock>;

explicit SpinLock(const LockBehavior lock_behavior) noexcept;

expected<void, LockError> lock_impl() noexcept;

expected<void, UnlockError> unlock_impl() noexcept;

expected<TryLock, TryLockError> 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<bool> m_recursive{false};
concurrent::Atomic<uint64_t> m_recursive_count{0};
concurrent::Atomic<std::thread::id> 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<void, Error> create(optional<SpinLock>& uninitializedLock) noexcept;
};

} // namespace concurrent
} // namespace iox

#endif // IOX_HOOFS_CONCURRENT_SYNC_SPIN_LOCK_HPP
117 changes: 117 additions & 0 deletions iceoryx_hoofs/concurrent/sync/source/spin_lock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// 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<void, SpinLockBuilder::Error>
SpinLockBuilder::create(optional<concurrent::SpinLock>& 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<void, LockError> SpinLock::lock_impl() noexcept
{
auto tid = std::this_thread::get_id();

if (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_tid.store(tid);
m_recursive_count.store(1);

return ok();
}

expected<void, UnlockError> SpinLock::unlock_impl() noexcept
{
auto tid = std::this_thread::get_id();

if (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_tid.store(std::thread::id());
m_lock_flag.clear(std::memory_order_release);
}

return ok();
}

expected<TryLock, TryLockError> SpinLock::try_lock_impl() noexcept
{
auto tid = std::this_thread::get_id();

if (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_tid.store(tid);
m_recursive_count.store(1);

return ok(TryLock::LOCK_SUCCEEDED);
}
return ok(TryLock::FAILED_TO_ACQUIRE_LOCK);
}

} // namespace concurrent
} // namespace iox
112 changes: 112 additions & 0 deletions iceoryx_hoofs/design/include/iox/lock_interface.hpp
Original file line number Diff line number Diff line change
@@ -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 <cstdint>

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 <typename Lock>
class LockInterface
{
public:
/// @brief Engages the lock.
/// @return When it fails it returns an enum describing the error.
expected<void, LockError> lock() noexcept
{
return static_cast<Lock*>(this)->lock_impl();
}

/// @brief Releases the lock.
/// @return When it fails it returns an enum describing the error.
expected<void, UnlockError> unlock() noexcept
{
return static_cast<Lock*>(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<TryLock, TryLockError> try_lock() noexcept
{
return static_cast<Lock*>(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
Loading

0 comments on commit c983dcc

Please sign in to comment.