summaryrefslogtreecommitdiffstats
path: root/library/cpp/yt
diff options
context:
space:
mode:
authorbabenko <[email protected]>2026-04-12 13:31:27 +0300
committerbabenko <[email protected]>2026-04-12 14:03:33 +0300
commit2f678ac94fa0e9aeffde6130e9015786e7ea4ed3 (patch)
tree7355bafd4df46c0670182161ef2c25980436e764 /library/cpp/yt
parent1dd39b59f3963c032eba42bffb547d849e33502e (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.h118
-rw-r--r--library/cpp/yt/containers/sentinel_optional.h114
-rw-r--r--library/cpp/yt/containers/unittests/sentinel_optional_ut.cpp291
-rw-r--r--library/cpp/yt/containers/unittests/ya.make1
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
)