From 2f678ac94fa0e9aeffde6130e9015786e7ea4ed3 Mon Sep 17 00:00:00 2001 From: babenko Date: Sun, 12 Apr 2026 13:31:27 +0300 Subject: YT-27910: Make some more fields in TServiceContext atomic commit_hash:24a87198c8fc53da983f9678679b990070e329b8 --- library/cpp/yt/containers/sentinel_optional-inl.h | 118 +++++++++ library/cpp/yt/containers/sentinel_optional.h | 114 ++++++++ .../containers/unittests/sentinel_optional_ut.cpp | 291 +++++++++++++++++++++ library/cpp/yt/containers/unittests/ya.make | 1 + 4 files changed, 524 insertions(+) create mode 100644 library/cpp/yt/containers/sentinel_optional-inl.h create mode 100644 library/cpp/yt/containers/sentinel_optional.h create mode 100644 library/cpp/yt/containers/unittests/sentinel_optional_ut.cpp (limited to 'library/cpp/yt') diff --git a/library/cpp/yt/containers/sentinel_optional-inl.h b/library/cpp/yt/containers/sentinel_optional-inl.h new file mode 100644 index 00000000000..c16f843f8a6 --- /dev/null +++ b/library/cpp/yt/containers/sentinel_optional-inl.h @@ -0,0 +1,118 @@ +#ifndef SENTINEL_OPTIONAL_INL_H_ +#error "Direct inclusion of this file is not allowed, include sentinel_optional.h" +// For the sake of sane code completion. +#include "sentinel_optional.h" +#endif + +#include + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template +constexpr TSentinelOptional::TSentinelOptional(std::nullopt_t) noexcept + : Value_(TSentinel::Sentinel) +{ } + +template +constexpr TSentinelOptional::TSentinelOptional(T value) noexcept + : Value_(value) +{ } + +template +constexpr TSentinelOptional::TSentinelOptional(std::optional opt) noexcept + : Value_(opt.has_value() ? *opt : TSentinel::Sentinel) +{ } + +template +constexpr TSentinelOptional& TSentinelOptional::operator=(std::nullopt_t) noexcept +{ + Value_ = TSentinel::Sentinel; + return *this; +} + +template +constexpr TSentinelOptional& TSentinelOptional::operator=(T value) noexcept +{ + Value_ = value; + return *this; +} + +template +constexpr bool TSentinelOptional::has_value() const noexcept +{ + return Value_ != TSentinel::Sentinel; +} + +template +constexpr TSentinelOptional::operator bool() const noexcept +{ + return has_value(); +} + +template +constexpr T& TSentinelOptional::operator*() noexcept +{ + return Value_; +} + +template +constexpr const T& TSentinelOptional::operator*() const noexcept +{ + return Value_; +} + +template +constexpr T* TSentinelOptional::operator->() noexcept +{ + return &Value_; +} + +template +constexpr const T* TSentinelOptional::operator->() const noexcept +{ + return &Value_; +} + +template +T& TSentinelOptional::value() noexcept +{ + YT_ASSERT(has_value()); + return Value_; +} + +template +const T& TSentinelOptional::value() const noexcept +{ + YT_ASSERT(has_value()); + return Value_; +} + +template +constexpr T TSentinelOptional::value_or(T default_value) const noexcept +{ + return has_value() ? Value_ : default_value; +} + +template +constexpr void TSentinelOptional::reset() noexcept +{ + Value_ = TSentinel::Sentinel; +} + +template +constexpr TSentinelOptional::operator std::optional() const noexcept +{ + return has_value() ? std::optional(Value_) : std::nullopt; +} + +template +constexpr bool TSentinelOptional::operator==(std::nullopt_t) const noexcept +{ + return !has_value(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/containers/sentinel_optional.h b/library/cpp/yt/containers/sentinel_optional.h new file mode 100644 index 00000000000..1d573550db9 --- /dev/null +++ b/library/cpp/yt/containers/sentinel_optional.h @@ -0,0 +1,114 @@ +#pragma once + +#include + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +//! Convenience sentinel traits for structural types (integers, enums, pointers). +/*! + * Usage: |TSentinelOptional>| + */ +template +struct TValueSentinel +{ + static constexpr auto Sentinel = V; +}; + +//////////////////////////////////////////////////////////////////////////////// + +//! A compact alternative to |std::optional| that uses a sentinel value +//! to represent the null state rather than an additional boolean field. +/*! + * The null state is represented by |TSentinel::Sentinel| of type |T|. + * The stored representation is exactly one |T|, so: + * sizeof(TSentinelOptional) == sizeof(T) + * alignof(TSentinelOptional) == alignof(T) + * + * Whenever |T| is trivially copyable the class is also trivially copyable, + * which guarantees that |std::atomic>| is lock-free + * if and only if |std::atomic| is lock-free. + * + * The interface is a drop-in replacement for |std::optional|. + * + * |TSentinel| must provide a |static constexpr T Sentinel| member. + * For structural types, use |TValueSentinel| as a convenience. + */ +template +class TSentinelOptional +{ +public: + using value_type = T; + + //! Constructs a null optional (stores the sentinel value). + constexpr TSentinelOptional() = default; + + //! Constructs a null optional. + constexpr TSentinelOptional(std::nullopt_t) noexcept; + + //! Constructs an optional holding |value|. + /*! + * \note The behavior is undefined if |value == TSentinel::Sentinel|. + */ + constexpr TSentinelOptional(T value) noexcept; + + //! Converts from |std::optional|; nullopt maps to the null state. + constexpr TSentinelOptional(std::optional opt) noexcept; + + constexpr TSentinelOptional(const TSentinelOptional&) = default; + constexpr TSentinelOptional(TSentinelOptional&&) = default; + + constexpr TSentinelOptional& operator=(const TSentinelOptional&) = default; + constexpr TSentinelOptional& operator=(TSentinelOptional&&) = default; + + //! Resets to null. + constexpr TSentinelOptional& operator=(std::nullopt_t) noexcept; + + //! Assigns |value|. + /*! + * \note The behavior is undefined if |value == TSentinel::Sentinel|. + */ + constexpr TSentinelOptional& operator=(T value) noexcept; + + //! Returns |true| iff the optional holds a value (i.e., the stored value differs from the sentinel). + [[nodiscard]] constexpr bool has_value() const noexcept; + + //! Returns |true| iff the optional holds a value. + constexpr explicit operator bool() const noexcept; + + //! Returns a reference to the held value; behavior is undefined if null. + constexpr T& operator*() noexcept; + constexpr const T& operator*() const noexcept; + + //! Returns a pointer to the held value; behavior is undefined if null. + constexpr T* operator->() noexcept; + constexpr const T* operator->() const noexcept; + + //! Returns a reference to the held value; aborts if null. + T& value() noexcept; + const T& value() const noexcept; + + //! Returns the held value if present, otherwise |default_value|. + constexpr T value_or(T default_value) const noexcept; + + //! Resets to null (equivalent to assigning |std::nullopt|). + constexpr void reset() noexcept; + + //! Converts to |std::optional|; the null state maps to nullopt. + constexpr operator std::optional() const noexcept; + + constexpr bool operator==(const TSentinelOptional& other) const = default; + [[nodiscard]] constexpr bool operator==(std::nullopt_t) const noexcept; + +private: + T Value_ = TSentinel::Sentinel; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define SENTINEL_OPTIONAL_INL_H_ +#include "sentinel_optional-inl.h" +#undef SENTINEL_OPTIONAL_INL_H_ diff --git a/library/cpp/yt/containers/unittests/sentinel_optional_ut.cpp b/library/cpp/yt/containers/unittests/sentinel_optional_ut.cpp new file mode 100644 index 00000000000..64ec5a04367 --- /dev/null +++ b/library/cpp/yt/containers/unittests/sentinel_optional_ut.cpp @@ -0,0 +1,291 @@ +#include + +#include + +#include +#include + +namespace NYT { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +using TIntOpt = TSentinelOptional>; + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TSentinelOptionalTest, DefaultConstructNull) +{ + TIntOpt opt; + EXPECT_FALSE(opt.has_value()); + EXPECT_FALSE(static_cast(opt)); + EXPECT_EQ(opt, std::nullopt); +} + +TEST(TSentinelOptionalTest, NulloptConstruct) +{ + TIntOpt opt(std::nullopt); + EXPECT_FALSE(opt.has_value()); + EXPECT_EQ(opt, std::nullopt); +} + +TEST(TSentinelOptionalTest, ValueConstruct) +{ + TIntOpt opt(42); + EXPECT_TRUE(opt.has_value()); + EXPECT_TRUE(static_cast(opt)); + EXPECT_EQ(*opt, 42); +} + +TEST(TSentinelOptionalTest, ZeroIsNotNull) +{ + TIntOpt opt(0); + EXPECT_TRUE(opt.has_value()); + EXPECT_EQ(*opt, 0); +} + +TEST(TSentinelOptionalTest, AssignValue) +{ + TIntOpt opt; + opt = 7; + EXPECT_TRUE(opt.has_value()); + EXPECT_EQ(*opt, 7); +} + +TEST(TSentinelOptionalTest, AssignNullopt) +{ + TIntOpt opt(7); + opt = std::nullopt; + EXPECT_FALSE(opt.has_value()); +} + +TEST(TSentinelOptionalTest, Reset) +{ + TIntOpt opt(7); + opt.reset(); + EXPECT_FALSE(opt.has_value()); + EXPECT_EQ(opt, std::nullopt); +} + +TEST(TSentinelOptionalTest, Dereference) +{ + TIntOpt opt(99); + EXPECT_EQ(*opt, 99); + *opt = 100; + EXPECT_EQ(*opt, 100); +} + +TEST(TSentinelOptionalTest, ArrowOperator) +{ + TSentinelOptional> opt(42); + EXPECT_EQ(*opt.operator->(), 42); +} + +TEST(TSentinelOptionalTest, ValueChecked) +{ + TIntOpt opt(5); + EXPECT_EQ(opt.value(), 5); + opt.value() = 10; + EXPECT_EQ(opt.value(), 10); +} + +TEST(TSentinelOptionalTest, ValueOr) +{ + TIntOpt opt; + EXPECT_EQ(opt.value_or(42), 42); + + opt = 7; + EXPECT_EQ(opt.value_or(42), 7); +} + +TEST(TSentinelOptionalTest, EqualityBetweenOptionals) +{ + TIntOpt a; + TIntOpt b; + EXPECT_EQ(a, b); + + a = 5; + EXPECT_NE(a, b); + + b = 5; + EXPECT_EQ(a, b); + + b = 6; + EXPECT_NE(a, b); +} + +TEST(TSentinelOptionalTest, EqualityWithNullopt) +{ + TIntOpt opt; + EXPECT_EQ(opt, std::nullopt); + EXPECT_EQ(std::nullopt, opt); + + opt = 1; + EXPECT_NE(opt, std::nullopt); + EXPECT_NE(std::nullopt, opt); +} + +TEST(TSentinelOptionalTest, CopyConstruct) +{ + TIntOpt a(3); + TIntOpt b = a; + EXPECT_EQ(*b, 3); + *a = 4; + EXPECT_EQ(*b, 3); // b is independent +} + +TEST(TSentinelOptionalTest, CopyAssign) +{ + TIntOpt a(3); + TIntOpt b; + b = a; + EXPECT_EQ(*b, 3); +} + +TEST(TSentinelOptionalTest, ConstAccess) +{ + const TIntOpt opt(77); + EXPECT_EQ(*opt, 77); + EXPECT_EQ(opt.value(), 77); + EXPECT_EQ(*opt.operator->(), 77); +} + +//////////////////////////////////////////////////////////////////////////////// +// Pointer sentinel. + +using TPtrOpt = TSentinelOptional>; + +TEST(TSentinelOptionalTest, PointerSentinel) +{ + TPtrOpt opt; + EXPECT_FALSE(opt.has_value()); + + int x = 42; + opt = &x; + EXPECT_TRUE(opt.has_value()); + EXPECT_EQ(*opt, &x); + EXPECT_EQ(**opt, 42); +} + +//////////////////////////////////////////////////////////////////////////////// +// Enum sentinel. + +enum class EColor +{ + Red, + Green, + Blue, + Unknown, +}; + +using TColorOpt = TSentinelOptional>; + +TEST(TSentinelOptionalTest, EnumSentinel) +{ + TColorOpt opt; + EXPECT_FALSE(opt.has_value()); + + opt = EColor::Red; + EXPECT_TRUE(opt.has_value()); + EXPECT_EQ(*opt, EColor::Red); + + opt.reset(); + EXPECT_FALSE(opt.has_value()); +} + +//////////////////////////////////////////////////////////////////////////////// +// Layout and atomic guarantees. + +TEST(TSentinelOptionalTest, SizeEqualsUnderlyingType) +{ + static_assert(sizeof(TIntOpt) == sizeof(int)); + static_assert(sizeof(TPtrOpt) == sizeof(int*)); + static_assert(sizeof(TColorOpt) == sizeof(EColor)); +} + +TEST(TSentinelOptionalTest, TriviallyCopyable) +{ + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_copyable_v); +} + +TEST(TSentinelOptionalTest, AtomicLockFree) +{ + // std::atomic> must be lock-free whenever + // std::atomic is lock-free. + if (std::atomic{}.is_lock_free()) { + EXPECT_TRUE((std::atomic{}.is_lock_free())); + } + if (std::atomic{}.is_lock_free()) { + EXPECT_TRUE((std::atomic{}.is_lock_free())); + } + if (std::atomic{}.is_lock_free()) { + EXPECT_TRUE((std::atomic{}.is_lock_free())); + } +} + +TEST(TSentinelOptionalTest, AtomicStoreLoad) +{ + std::atomic a; + a.store(TIntOpt{}); + EXPECT_FALSE(a.load().has_value()); + + a.store(TIntOpt{42}); + EXPECT_EQ(*a.load(), 42); + + a.store(std::nullopt); + EXPECT_FALSE(a.load().has_value()); +} + +//////////////////////////////////////////////////////////////////////////////// +// std::optional conversions. + +TEST(TSentinelOptionalTest, FromStdOptionalNull) +{ + TIntOpt opt = std::optional{}; + EXPECT_FALSE(opt.has_value()); + EXPECT_EQ(opt, std::nullopt); +} + +TEST(TSentinelOptionalTest, FromStdOptionalValue) +{ + TIntOpt opt = std::optional{42}; + EXPECT_TRUE(opt.has_value()); + EXPECT_EQ(*opt, 42); +} + +TEST(TSentinelOptionalTest, ToStdOptionalNull) +{ + TIntOpt opt; + std::optional stdOpt = opt; + EXPECT_FALSE(stdOpt.has_value()); +} + +TEST(TSentinelOptionalTest, ToStdOptionalValue) +{ + TIntOpt opt(7); + std::optional stdOpt = opt; + EXPECT_TRUE(stdOpt.has_value()); + EXPECT_EQ(*stdOpt, 7); +} + +TEST(TSentinelOptionalTest, RoundTripThroughStdOptional) +{ + TIntOpt original(99); + TIntOpt roundTripped = std::optional(original); + EXPECT_TRUE(roundTripped.has_value()); + EXPECT_EQ(*roundTripped, 99); +} + +TEST(TSentinelOptionalTest, RoundTripNullThroughStdOptional) +{ + TIntOpt original; + TIntOpt roundTripped = std::optional(original); + EXPECT_FALSE(roundTripped.has_value()); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT diff --git a/library/cpp/yt/containers/unittests/ya.make b/library/cpp/yt/containers/unittests/ya.make index 232fa2e0e5b..aa5aac875c8 100644 --- a/library/cpp/yt/containers/unittests/ya.make +++ b/library/cpp/yt/containers/unittests/ya.make @@ -9,6 +9,7 @@ SRCS( expiring_set_ut.cpp non_empty_ut.cpp ordered_hash_map_ut.cpp + sentinel_optional_ut.cpp sharded_set_ut.cpp ) -- cgit v1.3