From e671205f07549affd5f1c4bca36cdea46b50f193 Mon Sep 17 00:00:00 2001 From: Laurynas Biveinis Date: Wed, 5 Feb 2025 09:45:31 +0200 Subject: [PATCH] Improve optimistic lock Doxygen docs - Add optimistic-lock Doxygen topic - Move optimistic_lock.hpp file-level docs to this topic - Add docs for in_fake_critical_section.hpp, link from optimistic_lock.hpp as needed - Cleanup its declarations a little bit --- in_fake_critical_section.hpp | 118 +++++++++++++++++++++++------------ modules.dox | 4 ++ optimistic_lock.hpp | 42 +++++++++---- 3 files changed, 113 insertions(+), 51 deletions(-) diff --git a/in_fake_critical_section.hpp b/in_fake_critical_section.hpp index 7585b752..c770167a 100644 --- a/in_fake_critical_section.hpp +++ b/in_fake_critical_section.hpp @@ -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 !!! @@ -13,105 +27,131 @@ #include "global.hpp" // IWYU pragma: keep #include -#include 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-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-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 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 &) = - default; - constexpr in_fake_critical_section(in_fake_critical_section &&) 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 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 >> + /// Checks whether the wrapped pointer is `nullptr`. [[nodiscard, gnu::pure]] constexpr auto operator==( std::nullptr_t) const noexcept { return value == nullptr; } - template >> + /// 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 &) = delete; + in_fake_critical_section(in_fake_critical_section &&) noexcept = delete; + in_fake_critical_section &operator=(in_fake_critical_section &&) = delete; + private: + /// The wrapped value. T value; }; diff --git a/modules.dox b/modules.dox index e7008590..570cd97a 100644 --- a/modules.dox +++ b/modules.dox @@ -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. diff --git a/optimistic_lock.hpp b/optimistic_lock.hpp index 2fbce3b4..7929ae0b 100644 --- a/optimistic_lock.hpp +++ b/optimistic_lock.hpp @@ -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 @@ -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 @@ -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 @@ -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. @@ -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 class [[nodiscard]] in_critical_section final { @@ -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 &operator=(T new_value) noexcept { - store(new_value); - return *this; - } - /// Copy-assign another wrapped value. // NOLINTNEXTLINE(cert-oop54-cpp) - in_critical_section &operator=( - const in_critical_section &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); } @@ -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 &) = delete; - in_critical_section(in_critical_section &&) = delete; - void operator=(in_critical_section &&) = 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.