diff options
| author | babenko <[email protected]> | 2026-04-12 13:31:27 +0300 |
|---|---|---|
| committer | babenko <[email protected]> | 2026-04-12 14:03:33 +0300 |
| commit | 2f678ac94fa0e9aeffde6130e9015786e7ea4ed3 (patch) | |
| tree | 7355bafd4df46c0670182161ef2c25980436e764 /library/cpp/yt | |
| parent | 1dd39b59f3963c032eba42bffb547d849e33502e (diff) | |
YT-27910: Make some more fields in TServiceContext atomic
commit_hash:24a87198c8fc53da983f9678679b990070e329b8
Diffstat (limited to 'library/cpp/yt')
| -rw-r--r-- | library/cpp/yt/containers/sentinel_optional-inl.h | 118 | ||||
| -rw-r--r-- | library/cpp/yt/containers/sentinel_optional.h | 114 | ||||
| -rw-r--r-- | library/cpp/yt/containers/unittests/sentinel_optional_ut.cpp | 291 | ||||
| -rw-r--r-- | library/cpp/yt/containers/unittests/ya.make | 1 |
4 files changed, 524 insertions, 0 deletions
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 <library/cpp/yt/assert/assert.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T, class TSentinel> +constexpr TSentinelOptional<T, TSentinel>::TSentinelOptional(std::nullopt_t) noexcept + : Value_(TSentinel::Sentinel) +{ } + +template <class T, class TSentinel> +constexpr TSentinelOptional<T, TSentinel>::TSentinelOptional(T value) noexcept + : Value_(value) +{ } + +template <class T, class TSentinel> +constexpr TSentinelOptional<T, TSentinel>::TSentinelOptional(std::optional<T> opt) noexcept + : Value_(opt.has_value() ? *opt : TSentinel::Sentinel) +{ } + +template <class T, class TSentinel> +constexpr TSentinelOptional<T, TSentinel>& TSentinelOptional<T, TSentinel>::operator=(std::nullopt_t) noexcept +{ + Value_ = TSentinel::Sentinel; + return *this; +} + +template <class T, class TSentinel> +constexpr TSentinelOptional<T, TSentinel>& TSentinelOptional<T, TSentinel>::operator=(T value) noexcept +{ + Value_ = value; + return *this; +} + +template <class T, class TSentinel> +constexpr bool TSentinelOptional<T, TSentinel>::has_value() const noexcept +{ + return Value_ != TSentinel::Sentinel; +} + +template <class T, class TSentinel> +constexpr TSentinelOptional<T, TSentinel>::operator bool() const noexcept +{ + return has_value(); +} + +template <class T, class TSentinel> +constexpr T& TSentinelOptional<T, TSentinel>::operator*() noexcept +{ + return Value_; +} + +template <class T, class TSentinel> +constexpr const T& TSentinelOptional<T, TSentinel>::operator*() const noexcept +{ + return Value_; +} + +template <class T, class TSentinel> +constexpr T* TSentinelOptional<T, TSentinel>::operator->() noexcept +{ + return &Value_; +} + +template <class T, class TSentinel> +constexpr const T* TSentinelOptional<T, TSentinel>::operator->() const noexcept +{ + return &Value_; +} + +template <class T, class TSentinel> +T& TSentinelOptional<T, TSentinel>::value() noexcept +{ + YT_ASSERT(has_value()); + return Value_; +} + +template <class T, class TSentinel> +const T& TSentinelOptional<T, TSentinel>::value() const noexcept +{ + YT_ASSERT(has_value()); + return Value_; +} + +template <class T, class TSentinel> +constexpr T TSentinelOptional<T, TSentinel>::value_or(T default_value) const noexcept +{ + return has_value() ? Value_ : default_value; +} + +template <class T, class TSentinel> +constexpr void TSentinelOptional<T, TSentinel>::reset() noexcept +{ + Value_ = TSentinel::Sentinel; +} + +template <class T, class TSentinel> +constexpr TSentinelOptional<T, TSentinel>::operator std::optional<T>() const noexcept +{ + return has_value() ? std::optional<T>(Value_) : std::nullopt; +} + +template <class T, class TSentinel> +constexpr bool TSentinelOptional<T, TSentinel>::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 <optional> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +//! Convenience sentinel traits for structural types (integers, enums, pointers). +/*! + * Usage: |TSentinelOptional<int, TValueSentinel<-1>>| + */ +template <auto V> +struct TValueSentinel +{ + static constexpr auto Sentinel = V; +}; + +//////////////////////////////////////////////////////////////////////////////// + +//! A compact alternative to |std::optional<T>| 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<T, TSentinel>) == sizeof(T) + * alignof(TSentinelOptional<T, TSentinel>) == alignof(T) + * + * Whenever |T| is trivially copyable the class is also trivially copyable, + * which guarantees that |std::atomic<TSentinelOptional<T, TSentinel>>| is lock-free + * if and only if |std::atomic<T>| is lock-free. + * + * The interface is a drop-in replacement for |std::optional<T>|. + * + * |TSentinel| must provide a |static constexpr T Sentinel| member. + * For structural types, use |TValueSentinel<V>| as a convenience. + */ +template <class T, class TSentinel> +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<T>|; nullopt maps to the null state. + constexpr TSentinelOptional(std::optional<T> 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<T>|; the null state maps to nullopt. + constexpr operator std::optional<T>() 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 <library/cpp/testing/gtest/gtest.h> + +#include <library/cpp/yt/containers/sentinel_optional.h> + +#include <atomic> +#include <type_traits> + +namespace NYT { +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +using TIntOpt = TSentinelOptional<int, TValueSentinel<-1>>; + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TSentinelOptionalTest, DefaultConstructNull) +{ + TIntOpt opt; + EXPECT_FALSE(opt.has_value()); + EXPECT_FALSE(static_cast<bool>(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<bool>(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<int, TValueSentinel<0>> 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<int*, TValueSentinel<nullptr>>; + +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<EColor, TValueSentinel<EColor::Unknown>>; + +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<TIntOpt>); + static_assert(std::is_trivially_copyable_v<TPtrOpt>); + static_assert(std::is_trivially_copyable_v<TColorOpt>); +} + +TEST(TSentinelOptionalTest, AtomicLockFree) +{ + // std::atomic<TSentinelOptional<T, S>> must be lock-free whenever + // std::atomic<T> is lock-free. + if (std::atomic<int>{}.is_lock_free()) { + EXPECT_TRUE((std::atomic<TIntOpt>{}.is_lock_free())); + } + if (std::atomic<int*>{}.is_lock_free()) { + EXPECT_TRUE((std::atomic<TPtrOpt>{}.is_lock_free())); + } + if (std::atomic<EColor>{}.is_lock_free()) { + EXPECT_TRUE((std::atomic<TColorOpt>{}.is_lock_free())); + } +} + +TEST(TSentinelOptionalTest, AtomicStoreLoad) +{ + std::atomic<TIntOpt> 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<int>{}; + EXPECT_FALSE(opt.has_value()); + EXPECT_EQ(opt, std::nullopt); +} + +TEST(TSentinelOptionalTest, FromStdOptionalValue) +{ + TIntOpt opt = std::optional<int>{42}; + EXPECT_TRUE(opt.has_value()); + EXPECT_EQ(*opt, 42); +} + +TEST(TSentinelOptionalTest, ToStdOptionalNull) +{ + TIntOpt opt; + std::optional<int> stdOpt = opt; + EXPECT_FALSE(stdOpt.has_value()); +} + +TEST(TSentinelOptionalTest, ToStdOptionalValue) +{ + TIntOpt opt(7); + std::optional<int> stdOpt = opt; + EXPECT_TRUE(stdOpt.has_value()); + EXPECT_EQ(*stdOpt, 7); +} + +TEST(TSentinelOptionalTest, RoundTripThroughStdOptional) +{ + TIntOpt original(99); + TIntOpt roundTripped = std::optional<int>(original); + EXPECT_TRUE(roundTripped.has_value()); + EXPECT_EQ(*roundTripped, 99); +} + +TEST(TSentinelOptionalTest, RoundTripNullThroughStdOptional) +{ + TIntOpt original; + TIntOpt roundTripped = std::optional<int>(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 ) |
