Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve optimistic lock Doxygen docs #685

Merged
merged 1 commit into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 79 additions & 39 deletions in_fake_critical_section.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
// Copyright 2019-2021 Laurynas Biveinis
// Copyright 2019-2025 UnoDB contributors
#ifndef UNODB_DETAIL_IN_FAKE_CRITICAL_SECTION_HPP
#define UNODB_DETAIL_IN_FAKE_CRITICAL_SECTION_HPP

// TODO(laurynas): rename the file. fake_optimistic_lock.hpp ?
// TODO(laurynas): move everything to unodb::detail, together with
// optimistic_lock.hpp

/// \file
/// No-op ("fake") versions of the optimistic lock primitive, its read critical
/// section type, and the protected data declaration wrapper.
///
/// \ingroup optimistic-lock
///
/// The no-op versions or the real versions can be passed as template
/// parameters, resulting in code that can be compiled for both single-threaded
/// and concurrent use cases.

//
// CAUTION: [global.hpp] MUST BE THE FIRST INCLUDE IN ALL SOURCE AND
// HEADER FILES !!!
Expand All @@ -13,105 +27,131 @@
#include "global.hpp" // IWYU pragma: keep

#include <cstddef>
#include <type_traits>

namespace unodb {

// fake version of read_critical_section used to align unodb::db and
// unodb::olc_db code.
/// Fake version of optimistic_lock::read_critical_section used to align
/// unodb::db and unodb::olc_db code. All operations are no-ops.
// TODO(laurynas): move inside fake_lock?
class [[nodiscard]] fake_read_critical_section final {
public:
/// Trivially default-construct a fake read critical section.
fake_read_critical_section() noexcept = default;

[[nodiscard]] inline fake_read_critical_section &operator=(
/// Trivially destruct a fake read critical section.
~fake_read_critical_section() noexcept = default;

/// Move-assign from another fake read critical section, a no-op.
[[nodiscard]] fake_read_critical_section &operator=(
fake_read_critical_section &&) noexcept = default;

// Always equal.
[[nodiscard]] inline bool operator==(
const fake_read_critical_section &) const noexcept {
return true;
}
/// Check whether this fake read critical section is invalid after
/// construction, always succeeds.
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
[[nodiscard]] bool must_restart() const noexcept { return false; }

// Always succeeds.
[[nodiscard, gnu::flatten]] UNODB_DETAIL_FORCE_INLINE bool try_read_unlock()
const noexcept {
/// Check whether this fake read critical section is still valid, always
/// succeeds.
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
[[nodiscard]] bool check() UNODB_DETAIL_RELEASE_CONST noexcept {
return true;
}

// Always returns true since the lock is never updated.
[[nodiscard]] inline bool check() UNODB_DETAIL_RELEASE_CONST noexcept {
/// Compare with another read fake read critical section for equality, always
/// returning true.
[[nodiscard]] bool operator==(
const fake_read_critical_section &) const noexcept {
return true;
}

// Always returns false.
[[nodiscard]] inline bool must_restart() const noexcept { return false; }
/// Try to read unlock this read fake critical section, always succeeds.
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
[[nodiscard]] bool try_read_unlock() const noexcept { return true; }

fake_read_critical_section(const fake_read_critical_section &) = delete;
fake_read_critical_section(fake_read_critical_section &&) = delete;
fake_read_critical_section &operator=(const fake_read_critical_section &) =
delete;
}; // class fake_read_critical_section

// fake class for taking a lock.
/// Fake version of unodb::optimistic_lock. All operations are no-ops.
// TODO(laurynas): rename to fake_optimistic_lock.
class [[nodiscard]] fake_lock final {
public:
// Acquire and return a fake critical section for a fake lock.
/// Acquire and return an always-valid fake critical section for a fake lock.
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
[[nodiscard]] fake_read_critical_section try_read_lock() noexcept {
return fake_read_critical_section{};
}
}; // class fake_policy
}; // class fake_lock

// Provide access to T with in_critical_section<T>-like interface, except that
// loads and stores are direct instead of relaxed atomic. It enables having a
// common templatized implementation of single-threaded and OLC node algorithms.
/// Provide access to \a T with unodb::in_critical_section<T>-like interface,
/// except that loads and stores are direct instead of relaxed atomic. It
/// enables having a common templatized implementation of single-threaded and
/// OLC node algorithms.
template <typename T>
class [[nodiscard]] in_fake_critical_section final {
public:
/// Default construct the wrapped \a T value.
constexpr in_fake_critical_section() noexcept = default;

/// Construct the wrapped value from the passed \a value_.
// cppcheck-suppress noExplicitConstructor
// NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions)
constexpr in_fake_critical_section(T value_) noexcept : value{value_} {}
constexpr in_fake_critical_section(const in_fake_critical_section<T> &) =
default;
constexpr in_fake_critical_section(in_fake_critical_section<T> &&) noexcept =
default;

/// Destruct the wrapped value.
~in_fake_critical_section() noexcept = default;

// Enable when needed
in_fake_critical_section &operator=(in_fake_critical_section &&) = delete;

// Return nothing as we never chain assignments for now.
// NOLINTNEXTLINE(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator)
constexpr void operator=(T new_value) noexcept { value = new_value; }
/// Copy-assign another wrapped value.
// NOLINTNEXTLINE(cert-oop54-cpp)
constexpr in_fake_critical_section &operator=(
const in_fake_critical_section &new_value) noexcept {
value = new_value;
return *this;
}

// NOLINTNEXTLINE(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator)
constexpr void operator=(in_fake_critical_section<T> new_value) noexcept {
/// Assign \a new_value to the wrapped value.
constexpr in_fake_critical_section &operator=(T new_value) noexcept {
value = new_value;
return *this;
}

/// Pre-increment the wrapped value.
constexpr void operator++() noexcept { ++value; }

/// Pre-decrement the wrapped value.
constexpr void operator--() noexcept { --value; }

/// Post-decrement the wrapped value, returning the old value.
// NOLINTNEXTLINE(cert-dcl21-cpp)
constexpr T operator--(int) noexcept { return value--; }

template <typename T_ = T,
typename = std::enable_if_t<!std::is_integral_v<T_>>>
/// Checks whether the wrapped pointer is `nullptr`.
[[nodiscard, gnu::pure]] constexpr auto operator==(
std::nullptr_t) const noexcept {
return value == nullptr;
}

template <typename T_ = T,
typename = std::enable_if_t<!std::is_integral_v<T_>>>
/// Checks whether the wrapped pointer is not `nullptr`.
[[nodiscard, gnu::pure]] constexpr auto operator!=(
std::nullptr_t) const noexcept {
return value != nullptr;
}

/// Convert to the wrapped value, implicitly if needed.
// NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions)
[[nodiscard]] constexpr operator T() const noexcept { return value; }

/// Explicitly read the wrapped value.
[[nodiscard]] constexpr T load() const noexcept { return value; }

in_fake_critical_section(const in_fake_critical_section<T> &) = delete;
in_fake_critical_section(in_fake_critical_section<T> &&) noexcept = delete;
in_fake_critical_section &operator=(in_fake_critical_section &&) = delete;

private:
/// The wrapped value.
T value;
};

Expand Down
4 changes: 4 additions & 0 deletions modules.dox
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
///
/// See also [CONTRIBUTING.md](CONTRIBUTING.md).

/// \defgroup optimistic-lock Optimistic Locking
/// The optimistic lock primitive and associated internals.
/// \ingroup internal

/// \namespace unodb
/// The namespace for the public UnoDB API.

Expand Down
42 changes: 30 additions & 12 deletions optimistic_lock.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
/// \file
/// The optimistic lock.
///
/// \ingroup optimistic-lock

/// \addtogroup optimistic-lock
/// \{
/// # Overview
///
/// A version-based optimistic lock that supports single-writer/multiple-readers
Expand Down Expand Up @@ -203,6 +207,7 @@
///
/// The optimistic lock is also similar to Linux kernel sequential locks with
/// the addition of an obsolete state for data marked for reclamation.
/// \}

//
// CAUTION: [global.hpp] MUST BE THE FIRST INCLUDE IN ALL SOURCE AND
Expand Down Expand Up @@ -275,7 +280,11 @@ using version_tag_type = std::uint64_t;
/// comparing the version counter before and after the reads. Instances are
/// non-copyable and non-moveable.
///
/// See file-level documentation for usage examples and protocols.
/// See \ref optimistic-lock for usage examples and protocols.
///
/// To support reusing the same code for single-threaded context too, there is a
/// no-op counterpart: unodb::fake_optimistic_lock, enabling templatizing on the
/// lock type and passing either class as needed.
class [[nodiscard]] optimistic_lock final {
public:
/// Non-atomic lock word representation. Used for copying and manipulating
Expand Down Expand Up @@ -438,6 +447,10 @@ class [[nodiscard]] optimistic_lock final {
/// the RCS was created, and this has been detected by a try_read_unlock()
/// or check() call. The RCS is no longer valid.
///
/// To support reusing the same code for single-threaded contexts too, there
/// is a no-op counterpart: unodb::fake_read_critical_section, enabling
/// templatizing on the RCS type and passing the either class as needed.
///
/// Internally the obsolete state (and in the debug builds, the unlocked /
/// underlying lock write locked state too) is represented by
/// read_critical_section::lock == nullptr.
Expand Down Expand Up @@ -855,6 +868,11 @@ static_assert(sizeof(optimistic_lock) == 24);
/// as required by the optimistic lock memory model. The instances are
/// non-moveable and non-copy-constructable but the assignments both from the
/// wrapped values and plain value type are supported.
///
/// To support reusing the same code for single-threaded context too, there is a
/// no-op counterpart: unodb::in_fake_critical_section, enabling templatizing on
/// the wrapper type and passing either class as needed.
///
/// Implements the required set of transparent operations, extend as necessary.
template <typename T>
class [[nodiscard]] in_critical_section final {
Expand All @@ -870,20 +888,20 @@ class [[nodiscard]] in_critical_section final {
/// Destruct the wrapped value.
~in_critical_section() noexcept = default;

/// Assign \a new_value to the wrapped value.
in_critical_section<T> &operator=(T new_value) noexcept {
store(new_value);
return *this;
}

/// Copy-assign another wrapped value.
// NOLINTNEXTLINE(cert-oop54-cpp)
in_critical_section<T> &operator=(
const in_critical_section<T> &new_value) noexcept {
in_critical_section &operator=(
const in_critical_section &new_value) noexcept {
store(new_value.load());
return *this;
}

/// Assign \a new_value to the wrapped value.
in_critical_section &operator=(T new_value) noexcept {
store(new_value);
return *this;
}

/// Pre-increment the wrapped value.
void operator++() noexcept { store(load() + 1); }

Expand Down Expand Up @@ -925,9 +943,9 @@ class [[nodiscard]] in_critical_section final {
value.store(new_value, std::memory_order_relaxed);
}

in_critical_section(const in_critical_section<T> &) = delete;
in_critical_section(in_critical_section<T> &&) = delete;
void operator=(in_critical_section<T> &&) = delete;
in_critical_section(const in_critical_section &) = delete;
in_critical_section(in_critical_section &&) = delete;
void operator=(in_critical_section &&) = delete;

private:
/// The wrapped value.
Expand Down
Loading