summaryrefslogtreecommitdiffstats
path: root/library/cpp
diff options
context:
space:
mode:
authorAlexander Smirnov <[email protected]>2024-08-16 14:14:11 +0000
committerAlexander Smirnov <[email protected]>2024-08-16 14:14:11 +0000
commitb88486625399c8f2aa8e240f4a0a3a6ac7e24d02 (patch)
tree6e2d30be4e08d04de68fd8be4c61abb671bd30c4 /library/cpp
parent8dc96f72fdde8760e1808eda5fe39c5748e38d1b (diff)
parentf23ace9b7c0c2e8578421e3e640e3d1cc0fe381b (diff)
Merge branch 'rightlib' into mergelibs-240816-1413
Diffstat (limited to 'library/cpp')
-rw-r--r--library/cpp/yt/error/origin_attributes.cpp112
-rw-r--r--library/cpp/yt/error/origin_attributes.h83
-rw-r--r--library/cpp/yt/error/ya.make18
-rw-r--r--library/cpp/yt/global/access.h80
-rw-r--r--library/cpp/yt/global/impl.cpp126
-rw-r--r--library/cpp/yt/global/mock_modules/module1_defs/direct_access.h17
-rw-r--r--library/cpp/yt/global/mock_modules/module1_defs/test_variable.cpp38
-rw-r--r--library/cpp/yt/global/mock_modules/module1_defs/ya.make14
-rw-r--r--library/cpp/yt/global/mock_modules/module1_public/test_tag.h14
-rw-r--r--library/cpp/yt/global/mock_modules/module1_public/ya.make9
-rw-r--r--library/cpp/yt/global/mock_modules/module2_defs/direct_access.h22
-rw-r--r--library/cpp/yt/global/mock_modules/module2_defs/test_variable.cpp60
-rw-r--r--library/cpp/yt/global/mock_modules/module2_defs/ya.make16
-rw-r--r--library/cpp/yt/global/mock_modules/module2_public/test_tag.h15
-rw-r--r--library/cpp/yt/global/mock_modules/module2_public/ya.make9
-rw-r--r--library/cpp/yt/global/mock_modules/module3_defs/direct_access.h18
-rw-r--r--library/cpp/yt/global/mock_modules/module3_defs/test_variable.cpp41
-rw-r--r--library/cpp/yt/global/mock_modules/module3_defs/ya.make14
-rw-r--r--library/cpp/yt/global/mock_modules/module3_public/test_tag.h21
-rw-r--r--library/cpp/yt/global/mock_modules/module3_public/ya.make9
-rw-r--r--library/cpp/yt/global/unittests/global_variable/just_works_ut/main.cpp73
-rw-r--r--library/cpp/yt/global/unittests/global_variable/just_works_ut/ya.make16
-rw-r--r--library/cpp/yt/global/unittests/global_variable/missing_module_ut/main.cpp23
-rw-r--r--library/cpp/yt/global/unittests/global_variable/missing_module_ut/ya.make13
-rw-r--r--library/cpp/yt/global/unittests/global_variable/odr_violation_avoidance_ut/main.cpp41
-rw-r--r--library/cpp/yt/global/unittests/global_variable/odr_violation_avoidance_ut/ya.make16
-rw-r--r--library/cpp/yt/global/unittests/global_variable/two_modules_ut/main.cpp90
-rw-r--r--library/cpp/yt/global/unittests/global_variable/two_modules_ut/ya.make20
-rw-r--r--library/cpp/yt/global/variable-inl.h95
-rw-r--r--library/cpp/yt/global/variable.h53
-rw-r--r--library/cpp/yt/global/ya.make23
-rw-r--r--library/cpp/yt/memory/erased_storage-inl.h27
-rw-r--r--library/cpp/yt/memory/erased_storage.h36
-rw-r--r--library/cpp/yt/memory/unittests/erased_storage_ut.cpp22
-rw-r--r--library/cpp/yt/threading/public.h5
35 files changed, 1272 insertions, 17 deletions
diff --git a/library/cpp/yt/error/origin_attributes.cpp b/library/cpp/yt/error/origin_attributes.cpp
new file mode 100644
index 00000000000..5ff0b039339
--- /dev/null
+++ b/library/cpp/yt/error/origin_attributes.cpp
@@ -0,0 +1,112 @@
+#include "origin_attributes.h"
+
+#include <library/cpp/yt/assert/assert.h>
+
+#include <library/cpp/yt/misc/thread_name.h>
+#include <library/cpp/yt/misc/tls.h>
+
+#include <library/cpp/yt/string/format.h>
+
+#include <util/system/thread.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+YT_DEFINE_THREAD_LOCAL(bool, ErrorSanitizerEnabled, false);
+YT_DEFINE_THREAD_LOCAL(TInstant, ErrorSanitizerDatetimeOverride);
+YT_DEFINE_THREAD_LOCAL(TSharedRef, ErrorSanitizerLocalHostNameOverride);
+
+TErrorSanitizerGuard::TErrorSanitizerGuard(TInstant datetimeOverride, TSharedRef localHostNameOverride)
+ : SavedEnabled_(ErrorSanitizerEnabled())
+ , SavedDatetimeOverride_(ErrorSanitizerDatetimeOverride())
+ , SavedLocalHostNameOverride_(ErrorSanitizerLocalHostNameOverride())
+{
+ ErrorSanitizerEnabled() = true;
+ ErrorSanitizerDatetimeOverride() = datetimeOverride;
+ ErrorSanitizerLocalHostNameOverride() = std::move(localHostNameOverride);
+}
+
+TErrorSanitizerGuard::~TErrorSanitizerGuard()
+{
+ YT_ASSERT(ErrorSanitizerEnabled());
+
+ ErrorSanitizerEnabled() = SavedEnabled_;
+ ErrorSanitizerDatetimeOverride() = SavedDatetimeOverride_;
+ ErrorSanitizerLocalHostNameOverride() = std::move(SavedLocalHostNameOverride_);
+}
+
+bool IsErrorSanitizerEnabled() noexcept
+{
+ return ErrorSanitizerEnabled();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool TOriginAttributes::operator==(const TOriginAttributes& other) const noexcept
+{
+ return
+ Host == other.Host &&
+ Datetime == other.Datetime &&
+ Pid == other.Pid &&
+ Tid == other.Tid &&
+ ExtensionData == other.ExtensionData;
+}
+
+void TOriginAttributes::Capture()
+{
+ if (ErrorSanitizerEnabled()) {
+ Datetime = ErrorSanitizerDatetimeOverride();
+ HostHolder = ErrorSanitizerLocalHostNameOverride();
+ Host = HostHolder.empty() ? TStringBuf() : TStringBuf(HostHolder.Begin(), HostHolder.End());
+ return;
+ }
+
+ Datetime = TInstant::Now();
+ Pid = GetPID();
+ Tid = TThread::CurrentThreadId();
+ ThreadName = GetCurrentThreadName();
+ ExtensionData = NDetail::GetExtensionData();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+std::optional<TOriginAttributes::TErasedExtensionData> GetExtensionData()
+{
+ using TFunctor = TOriginAttributes::TErasedExtensionData(*)();
+
+ if (auto strong = NGlobal::GetErasedVariable(GetExtensionDataTag)) {
+ return strong->AsConcrete<TFunctor>()();
+ }
+ return std::nullopt;
+}
+
+TString FormatOrigin(const TOriginAttributes& attributes)
+{
+ using TFunctor = TString(*)(const TOriginAttributes&);
+
+ if (auto strong = NGlobal::GetErasedVariable(FormatOriginTag)) {
+ return strong->AsConcrete<TFunctor>()(attributes);
+ }
+
+ return Format(
+ "%v (pid %v, thread %v)",
+ attributes.Host,
+ attributes.Pid,
+ MakeFormatterWrapper([&] (auto* builder) {
+ auto threadName = attributes.ThreadName.ToStringBuf();
+ if (threadName.empty()) {
+ FormatValue(builder, attributes.Tid, "v");
+ return;
+ }
+ FormatValue(builder, threadName, "v");
+ }));
+}
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/error/origin_attributes.h b/library/cpp/yt/error/origin_attributes.h
new file mode 100644
index 00000000000..05815c1e38e
--- /dev/null
+++ b/library/cpp/yt/error/origin_attributes.h
@@ -0,0 +1,83 @@
+#pragma once
+
+#include <library/cpp/yt/global/access.h>
+
+#include <library/cpp/yt/memory/ref.h>
+
+#include <library/cpp/yt/misc/guid.h>
+#include <library/cpp/yt/misc/thread_name.h>
+
+#include <library/cpp/yt/threading/public.h>
+
+#include <util/datetime/base.h>
+
+#include <util/system/getpid.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! When this guard is set, newly created errors do not have non-deterministic
+//! system attributes and have "datetime" and "host" attributes overridden with a given values.
+class TErrorSanitizerGuard
+ : public TNonCopyable
+{
+public:
+ TErrorSanitizerGuard(TInstant datetimeOverride, TSharedRef localHostNameOverride);
+ ~TErrorSanitizerGuard();
+
+private:
+ const bool SavedEnabled_;
+ const TInstant SavedDatetimeOverride_;
+ const TSharedRef SavedLocalHostNameOverride_;
+};
+
+bool IsErrorSanitizerEnabled() noexcept;
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TOriginAttributes
+{
+ static constexpr size_t ExtensionDataByteSizeCap = 64;
+ using TErasedExtensionData = TErasedStorage<ExtensionDataByteSizeCap>;
+
+ TProcessId Pid;
+
+ NThreading::TThreadId Tid;
+ TThreadName ThreadName;
+
+ TInstant Datetime;
+
+ TSharedRef HostHolder;
+ mutable TStringBuf Host;
+
+ // Opaque storage for data from yt/yt/core.
+ // Currently may contain FiberId, TraceId, SpandId.
+ std::optional<TErasedExtensionData> ExtensionData;
+
+ bool operator==(const TOriginAttributes& other) const noexcept;
+
+ void Capture();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+inline constexpr NGlobal::TVariableTag GetExtensionDataTag = {};
+inline constexpr NGlobal::TVariableTag FormatOriginTag = {};
+inline constexpr NGlobal::TVariableTag ExtractFromDictionaryTag = {};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// These are "weak" symbols.
+// NB(arkady-e1ppa): ExtractFromDictionary symbol is left in yt/yt/core/misc/origin_attributes
+// because it depends on ytree for now.
+std::optional<TOriginAttributes::TErasedExtensionData> GetExtensionData();
+TString FormatOrigin(const TOriginAttributes& attributes);
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/error/ya.make b/library/cpp/yt/error/ya.make
new file mode 100644
index 00000000000..f5bd9461993
--- /dev/null
+++ b/library/cpp/yt/error/ya.make
@@ -0,0 +1,18 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+PEERDIR(
+ library/cpp/yt/assert
+ library/cpp/yt/global
+ library/cpp/yt/memory
+ library/cpp/yt/misc
+ library/cpp/yt/threading
+ library/cpp/yt/string
+)
+
+SRCS(
+ origin_attributes.cpp
+)
+
+END()
diff --git a/library/cpp/yt/global/access.h b/library/cpp/yt/global/access.h
new file mode 100644
index 00000000000..e746f80c8ba
--- /dev/null
+++ b/library/cpp/yt/global/access.h
@@ -0,0 +1,80 @@
+#pragma once
+
+#include <library/cpp/yt/memory/erased_storage.h>
+
+#include <library/cpp/yt/misc/strong_typedef.h>
+
+#include <atomic>
+
+namespace NYT::NGlobal {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+class TGlobalVariablesRegistry;
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline constexpr size_t GlobalVariableMaxByteSize = 32;
+using TErasedStorage = NYT::TErasedStorage<GlobalVariableMaxByteSize>;
+
+// NB(arkady-e1ppa): Accessor must ensure thread-safety on its own.
+using TAccessor = TErasedStorage(*)() noexcept;
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Usage:
+/*
+ * // public.h file:
+ * // NB: It's important to mark it inline for linker to deduplicate
+ * // addresses accross different UT's.
+ * inline constexpr NGlobal::TVariableTag MyGlobalVarTag = {};
+ *
+ *
+ *
+ * // some_stuff.cpp file
+ *
+ * TErasedStorage GetMyVar()
+ * {
+ * // definition here
+ * }
+ * NGlobal::Variable<int> MyGlobalVar{MyGlobalVarTag, &GetMyVar};
+ *
+ *
+ *
+ * // other_stuff.cpp file
+ *
+ *
+ * int ReadMyVar()
+ * {
+ * auto erased = NGlobal::GetErasedVariable(MyGlobalVarTag);
+ * return erased->AsConcrete<int>();
+ * }
+ */
+class TVariableTag
+{
+public:
+ TVariableTag() = default;
+
+ TVariableTag(const TVariableTag& other) = delete;
+ TVariableTag& operator=(const TVariableTag& other) = delete;
+
+private:
+ friend class ::NYT::NGlobal::NDetail::TGlobalVariablesRegistry;
+
+ mutable std::atomic<bool> Initialized_ = false;
+ mutable std::atomic<int> Key_ = -1;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Defined in impl.cpp.
+// |std::nullopt| iff global variable with a given tag is not present.
+std::optional<TErasedStorage> GetErasedVariable(const TVariableTag& tag);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NGlobal
diff --git a/library/cpp/yt/global/impl.cpp b/library/cpp/yt/global/impl.cpp
new file mode 100644
index 00000000000..033eedca345
--- /dev/null
+++ b/library/cpp/yt/global/impl.cpp
@@ -0,0 +1,126 @@
+#include "access.h"
+#include "variable.h"
+
+#include <library/cpp/yt/memory/leaky_singleton.h>
+
+#include <array>
+
+namespace NYT::NGlobal {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+inline constexpr int MaxTrackedGlobalVariables = 32;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TGlobalVariablesRegistry
+{
+public:
+ static TGlobalVariablesRegistry* Get()
+ {
+ return LeakySingleton<TGlobalVariablesRegistry>();
+ }
+
+ void RegisterAccessor(const TVariableTag& tag, TAccessor accessor)
+ {
+ if (!tag.Initialized_.exchange(true, std::memory_order::relaxed)) { // (a)
+ DoRegisterAccessor(tag, accessor);
+ return;
+ }
+
+ TryVerifyingExistingAccessor(tag, accessor);
+ }
+
+ std::optional<TErasedStorage> GetVariable(const TVariableTag& tag)
+ {
+ auto key = tag.Key_.load(std::memory_order::acquire); // (e)
+ if (key != -1) {
+ return Accessors_[key](); // (f)
+ }
+
+ return std::nullopt;
+ }
+
+private:
+ std::atomic<int> KeyGenerator_ = 0;
+ std::array<TAccessor, MaxTrackedGlobalVariables> Accessors_;
+
+ void DoRegisterAccessor(const TVariableTag& tag, TAccessor accessor)
+ {
+ // Get id -> place accessor -> store id
+ auto key = KeyGenerator_.fetch_add(1, std::memory_order::relaxed); // (b)
+
+ YT_VERIFY(key < MaxTrackedGlobalVariables);
+
+ Accessors_[key] = accessor; // (c)
+
+ tag.Key_.store(key, std::memory_order::release); // (d)
+ }
+
+ void TryVerifyingExistingAccessor(const TVariableTag& tag, TAccessor accessor)
+ {
+ auto key = tag.Key_.load(std::memory_order::acquire); // (e')
+ if (key == -1) {
+ // Accessor is about to be set.
+
+ // In order to avoid deadlock caused by forks
+ // we just leave. We could try acquiring fork
+ // locks here but this makes our check too expensive
+ // to be bothered.
+ return;
+ }
+
+ // Accessor has been already set -> safe to read it.
+ YT_VERIFY(Accessors_[key] == accessor); // (f')
+ }
+};
+
+// (arkady-e1ppa): Memory orders:
+/*
+ We have two scenarios: 2 writes and write & read:
+
+ 2 writes: Accessors_ is protected via Initialized_ flag
+ and KeyGenerator_ counter.
+ 1) RMW (a) reads the last value in modification order
+ thus relaxed is enough to ensure <= 1 threads registering
+ per Tag.
+ 2) KeyGenerator_ uses the same logic (see (b))
+ to ensure <= 1 threads registering per index in array.
+
+ If there are two writes per tag, then there is a "losing"
+ thread which read Initialized_ // true. For all intents
+ and purposes TryVerifyingExistingAccessor call is identical
+ to GetVariable call.
+
+ write & read: Relevant execution is below
+ W^na(Accessors_[id], 0x0) // Ctor
+ T1(Register) T2(Read)
+ W^na(Accessors_[id], 0x42) (c) R^acq(Key_, id) (e)
+ W^rel(Key_, id) (d) R^na(Accessors_[id], 0x42) (f)
+
+ (d) -rf-> (e) => (d) -SW-> (e). Since (c) -SB-> (d) and (e) -SB-> (f)
+ we have (c) -strongly HB-> (f) (strongly happens before). Thus we must
+ read 0x42 from Accessors_[id] (and not 0x0 which was written in ctor).
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+
+void RegisterVariable(const TVariableTag& tag, TAccessor accessor)
+{
+ TGlobalVariablesRegistry::Get()->RegisterAccessor(tag, accessor);
+}
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::optional<TErasedStorage> GetErasedVariable(const TVariableTag& tag)
+{
+ return NDetail::TGlobalVariablesRegistry::Get()->GetVariable(tag);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NGlobal
diff --git a/library/cpp/yt/global/mock_modules/module1_defs/direct_access.h b/library/cpp/yt/global/mock_modules/module1_defs/direct_access.h
new file mode 100644
index 00000000000..abd536474c2
--- /dev/null
+++ b/library/cpp/yt/global/mock_modules/module1_defs/direct_access.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <cstdint>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+int GetTestVariable1();
+void SetTestVariable1(int val);
+
+int GetTlsVariable();
+void SetTlsVariable(int val);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/global/mock_modules/module1_defs/test_variable.cpp b/library/cpp/yt/global/mock_modules/module1_defs/test_variable.cpp
new file mode 100644
index 00000000000..78912be6609
--- /dev/null
+++ b/library/cpp/yt/global/mock_modules/module1_defs/test_variable.cpp
@@ -0,0 +1,38 @@
+#include "direct_access.h"
+
+#include <library/cpp/yt/global/mock_modules/module1_public/test_tag.h>
+
+#include <library/cpp/yt/global/variable.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+YT_DEFINE_TRACKED_GLOBAL(int, TestVariable1, TestTag1, 42);
+YT_DEFINE_TRACKED_THREAD_LOCAL(int, TlsVariable, ThreadLocalTag, 0);
+
+////////////////////////////////////////////////////////////////////////////////
+
+int GetTestVariable1()
+{
+ return TestVariable1.Get();
+}
+
+void SetTestVariable1(int val)
+{
+ TestVariable1.Set(val);
+}
+
+int GetTlsVariable()
+{
+ return TlsVariable();
+}
+
+void SetTlsVariable(int val)
+{
+ TlsVariable() = val;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/global/mock_modules/module1_defs/ya.make b/library/cpp/yt/global/mock_modules/module1_defs/ya.make
new file mode 100644
index 00000000000..7152eb10b2a
--- /dev/null
+++ b/library/cpp/yt/global/mock_modules/module1_defs/ya.make
@@ -0,0 +1,14 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+SRC(
+ test_variable.cpp
+)
+
+PEERDIR(
+ library/cpp/yt/global
+ library/cpp/yt/global/mock_modules/module1_public
+)
+
+END()
diff --git a/library/cpp/yt/global/mock_modules/module1_public/test_tag.h b/library/cpp/yt/global/mock_modules/module1_public/test_tag.h
new file mode 100644
index 00000000000..0b3ed55ebd6
--- /dev/null
+++ b/library/cpp/yt/global/mock_modules/module1_public/test_tag.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <library/cpp/yt/global/access.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline constexpr NGlobal::TVariableTag TestTag1 = {};
+inline constexpr NGlobal::TVariableTag ThreadLocalTag = {};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/global/mock_modules/module1_public/ya.make b/library/cpp/yt/global/mock_modules/module1_public/ya.make
new file mode 100644
index 00000000000..524e22b97ee
--- /dev/null
+++ b/library/cpp/yt/global/mock_modules/module1_public/ya.make
@@ -0,0 +1,9 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+PEERDIR(
+ library/cpp/yt/global
+)
+
+END()
diff --git a/library/cpp/yt/global/mock_modules/module2_defs/direct_access.h b/library/cpp/yt/global/mock_modules/module2_defs/direct_access.h
new file mode 100644
index 00000000000..d5f47c1be7e
--- /dev/null
+++ b/library/cpp/yt/global/mock_modules/module2_defs/direct_access.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <library/cpp/yt/misc/guid.h>
+
+#include <array>
+#include <cstdint>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TGuid GetTestVariable2();
+
+void SetTestVariable2(TGuid val);
+
+std::array<int, 4> GetTestVariable3();
+
+void SetTestVariable3(std::array<int, 4> val);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/global/mock_modules/module2_defs/test_variable.cpp b/library/cpp/yt/global/mock_modules/module2_defs/test_variable.cpp
new file mode 100644
index 00000000000..3db411bcb5a
--- /dev/null
+++ b/library/cpp/yt/global/mock_modules/module2_defs/test_variable.cpp
@@ -0,0 +1,60 @@
+#include "direct_access.h"
+
+#include <library/cpp/yt/global/mock_modules/module2_public/test_tag.h>
+
+#include <library/cpp/yt/global/variable.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NGlobal::TErasedStorage GetGlobal2() noexcept;
+
+NGlobal::TErasedStorage GetGlobal3() noexcept;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static NGlobal::TVariable<TGuid> TestVariable2{TestTag2, &GetGlobal2, TGuid{42, 77}};
+
+static NGlobal::TVariable<std::array<int, 4>> TestVariable3{
+ TestTag3,
+ &GetGlobal3,
+ std::array{0, 0, 42, 77}};
+
+////////////////////////////////////////////////////////////////////////////////
+
+NGlobal::TErasedStorage GetGlobal2() noexcept
+{
+ return NGlobal::TErasedStorage{TestVariable2.Get()};
+}
+
+NGlobal::TErasedStorage GetGlobal3() noexcept
+{
+ return NGlobal::TErasedStorage{TestVariable3.Get()};
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TGuid GetTestVariable2()
+{
+ return TestVariable2.Get();
+}
+
+void SetTestVariable2(TGuid val)
+{
+ TestVariable2.Set(val);
+}
+
+std::array<int, 4> GetTestVariable3()
+{
+ return TestVariable3.Get();
+}
+
+void SetTestVariable3(std::array<int, 4> val)
+{
+ TestVariable3.Set(val);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/global/mock_modules/module2_defs/ya.make b/library/cpp/yt/global/mock_modules/module2_defs/ya.make
new file mode 100644
index 00000000000..6692debb2f9
--- /dev/null
+++ b/library/cpp/yt/global/mock_modules/module2_defs/ya.make
@@ -0,0 +1,16 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+SRC(
+ test_variable.cpp
+)
+
+PEERDIR(
+ library/cpp/yt/misc
+
+ library/cpp/yt/global
+ library/cpp/yt/global/mock_modules/module2_public
+)
+
+END()
diff --git a/library/cpp/yt/global/mock_modules/module2_public/test_tag.h b/library/cpp/yt/global/mock_modules/module2_public/test_tag.h
new file mode 100644
index 00000000000..3578368dcd7
--- /dev/null
+++ b/library/cpp/yt/global/mock_modules/module2_public/test_tag.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <library/cpp/yt/global/access.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline constexpr NGlobal::TVariableTag TestTag2 = {};
+
+inline constexpr NGlobal::TVariableTag TestTag3 = {};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/global/mock_modules/module2_public/ya.make b/library/cpp/yt/global/mock_modules/module2_public/ya.make
new file mode 100644
index 00000000000..524e22b97ee
--- /dev/null
+++ b/library/cpp/yt/global/mock_modules/module2_public/ya.make
@@ -0,0 +1,9 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+PEERDIR(
+ library/cpp/yt/global
+)
+
+END()
diff --git a/library/cpp/yt/global/mock_modules/module3_defs/direct_access.h b/library/cpp/yt/global/mock_modules/module3_defs/direct_access.h
new file mode 100644
index 00000000000..3a2a4e24c46
--- /dev/null
+++ b/library/cpp/yt/global/mock_modules/module3_defs/direct_access.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <library/cpp/yt/misc/guid.h>
+
+#include <array>
+#include <cstdint>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::array<int, 2> GetTestVariable3();
+
+void SetTestVariable3(std::array<int, 2> val);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/global/mock_modules/module3_defs/test_variable.cpp b/library/cpp/yt/global/mock_modules/module3_defs/test_variable.cpp
new file mode 100644
index 00000000000..a02fd15130b
--- /dev/null
+++ b/library/cpp/yt/global/mock_modules/module3_defs/test_variable.cpp
@@ -0,0 +1,41 @@
+#include "direct_access.h"
+
+#include <library/cpp/yt/global/mock_modules/module3_public/test_tag.h>
+
+#include <library/cpp/yt/global/variable.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+NGlobal::TErasedStorage GetGlobal3() noexcept;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static NGlobal::TVariable<std::array<int, 2>> TestVariable3{
+ TestTag3,
+ &GetGlobal3,
+ std::array{11, 22}};
+
+////////////////////////////////////////////////////////////////////////////////
+
+NGlobal::TErasedStorage GetGlobal3() noexcept
+{
+ return NGlobal::TErasedStorage{TestVariable3.Get()};
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+std::array<int, 2> GetTestVariable3()
+{
+ return TestVariable3.Get();
+}
+
+void SetTestVariable3(std::array<int, 2> val)
+{
+ TestVariable3.Set(val);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/global/mock_modules/module3_defs/ya.make b/library/cpp/yt/global/mock_modules/module3_defs/ya.make
new file mode 100644
index 00000000000..9fbc3104889
--- /dev/null
+++ b/library/cpp/yt/global/mock_modules/module3_defs/ya.make
@@ -0,0 +1,14 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+SRC(
+ test_variable.cpp
+)
+
+PEERDIR(
+ library/cpp/yt/global
+ library/cpp/yt/global/mock_modules/module3_public
+)
+
+END()
diff --git a/library/cpp/yt/global/mock_modules/module3_public/test_tag.h b/library/cpp/yt/global/mock_modules/module3_public/test_tag.h
new file mode 100644
index 00000000000..a7e59f2762c
--- /dev/null
+++ b/library/cpp/yt/global/mock_modules/module3_public/test_tag.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <library/cpp/yt/global/access.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB(arkady-e1ppa): This tag name conflicts with the one in module2.
+// However, if module2_defs is not linked at the same time as
+// module3_defs, you are guaranteed to read global from
+// module3_defs.
+// If you happen to mistakenly link module2_defs
+// instead of module3_defs, you will actually
+// read erased varibale from module_defs.
+// Be careful!
+inline constexpr NGlobal::TVariableTag TestTag3 = {};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/global/mock_modules/module3_public/ya.make b/library/cpp/yt/global/mock_modules/module3_public/ya.make
new file mode 100644
index 00000000000..524e22b97ee
--- /dev/null
+++ b/library/cpp/yt/global/mock_modules/module3_public/ya.make
@@ -0,0 +1,9 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+PEERDIR(
+ library/cpp/yt/global
+)
+
+END()
diff --git a/library/cpp/yt/global/unittests/global_variable/just_works_ut/main.cpp b/library/cpp/yt/global/unittests/global_variable/just_works_ut/main.cpp
new file mode 100644
index 00000000000..86189444043
--- /dev/null
+++ b/library/cpp/yt/global/unittests/global_variable/just_works_ut/main.cpp
@@ -0,0 +1,73 @@
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <library/cpp/yt/global/access.h>
+
+#include <library/cpp/yt/global/mock_modules/module1_defs/direct_access.h>
+#include <library/cpp/yt/global/mock_modules/module1_public/test_tag.h>
+
+#include <optional>
+#include <thread>
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TGlobalVariableTest, JustWorks)
+{
+ auto erasedVar = NGlobal::GetErasedVariable(TestTag1);
+ EXPECT_TRUE(erasedVar.has_value());
+
+ auto concreteVar = erasedVar->AsConcrete<int>();
+ EXPECT_EQ(concreteVar, GetTestVariable1());
+
+ SetTestVariable1(12344);
+
+ // NB: We copied variable, not a reference to it!
+ EXPECT_EQ(concreteVar, erasedVar->AsConcrete<int>());
+
+ EXPECT_EQ(NGlobal::GetErasedVariable(TestTag1)->AsConcrete<int>(), 12344);
+}
+
+TEST(TGlobalVariableTest, MissingTag)
+{
+ static constexpr NGlobal::TVariableTag MissingTag = {};
+ EXPECT_FALSE(NGlobal::GetErasedVariable(MissingTag));
+}
+
+TEST(TGlobalVariableTest, ThreadLocal)
+{
+ auto ensureConstructed = [] {
+ // NB: tls variable is constructed only after
+ // being referred to for the first time.
+ auto val = GetTlsVariable();
+ ++val;
+ Y_UNUSED(val);
+ };
+
+ auto checkTls = [&ensureConstructed] (int val) {
+ ensureConstructed();
+
+ auto erasedVar = NGlobal::GetErasedVariable(ThreadLocalTag);
+ EXPECT_TRUE(erasedVar);
+ EXPECT_EQ(erasedVar->AsConcrete<int>(), 0);
+
+ EXPECT_EQ(GetTlsVariable(), 0);
+ SetTlsVariable(val);
+
+ EXPECT_EQ(erasedVar->AsConcrete<int>(), 0);
+ EXPECT_EQ(NGlobal::GetErasedVariable(ThreadLocalTag)->AsConcrete<int>(), val);
+ };
+
+ checkTls(42);
+
+ for (int idx = 0; idx < 42; ++idx) {
+ auto thread = std::thread(std::bind(checkTls, idx << 2));
+ thread.join();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/global/unittests/global_variable/just_works_ut/ya.make b/library/cpp/yt/global/unittests/global_variable/just_works_ut/ya.make
new file mode 100644
index 00000000000..570ab888dcc
--- /dev/null
+++ b/library/cpp/yt/global/unittests/global_variable/just_works_ut/ya.make
@@ -0,0 +1,16 @@
+GTEST(unittester-library-global-just-works)
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+SRCS(
+ main.cpp
+)
+
+PEERDIR(
+ library/cpp/yt/global
+
+ library/cpp/yt/global/mock_modules/module1_public
+ library/cpp/yt/global/mock_modules/module1_defs
+)
+
+END()
diff --git a/library/cpp/yt/global/unittests/global_variable/missing_module_ut/main.cpp b/library/cpp/yt/global/unittests/global_variable/missing_module_ut/main.cpp
new file mode 100644
index 00000000000..355bb64d788
--- /dev/null
+++ b/library/cpp/yt/global/unittests/global_variable/missing_module_ut/main.cpp
@@ -0,0 +1,23 @@
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <library/cpp/yt/global/access.h>
+
+#include <library/cpp/yt/global/mock_modules/module1_public/test_tag.h>
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB: Module containing global variable definition is not
+// included in peerdirs. We expect it to be invisible from
+// here.
+TEST(TGlobalVariableTest, MissingModule)
+{
+ EXPECT_FALSE(NGlobal::GetErasedVariable(TestTag1));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/global/unittests/global_variable/missing_module_ut/ya.make b/library/cpp/yt/global/unittests/global_variable/missing_module_ut/ya.make
new file mode 100644
index 00000000000..f99a5964551
--- /dev/null
+++ b/library/cpp/yt/global/unittests/global_variable/missing_module_ut/ya.make
@@ -0,0 +1,13 @@
+GTEST(unittester-library-global-missing-module)
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+SRCS(
+ main.cpp
+)
+
+PEERDIR(
+ library/cpp/yt/global/mock_modules/module1_public
+)
+
+END()
diff --git a/library/cpp/yt/global/unittests/global_variable/odr_violation_avoidance_ut/main.cpp b/library/cpp/yt/global/unittests/global_variable/odr_violation_avoidance_ut/main.cpp
new file mode 100644
index 00000000000..d872763bf9a
--- /dev/null
+++ b/library/cpp/yt/global/unittests/global_variable/odr_violation_avoidance_ut/main.cpp
@@ -0,0 +1,41 @@
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <library/cpp/yt/global/access.h>
+
+#include <library/cpp/yt/global/mock_modules/module3_defs/direct_access.h>
+#include <library/cpp/yt/global/mock_modules/module3_public/test_tag.h>
+
+#include <array>
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T, size_t N>
+bool Equal(const std::array<T, N>& left, const std::array<T, N>& right)
+{
+ return [&] <size_t... Idx> (std::index_sequence<Idx...>) {
+ return ([&] {
+ return left[Idx] == right[Idx];
+ } () && ...);
+ } (std::make_index_sequence<N>());
+}
+
+TEST(TGlobalVariableTest, NoOdrViolation)
+{
+ auto erasedVar = NGlobal::GetErasedVariable(TestTag3);
+ EXPECT_TRUE(erasedVar);
+
+ EXPECT_TRUE(Equal(erasedVar->AsConcrete<std::array<int, 2>>(), std::array<int, 2>{11, 22}));
+
+ SetTestVariable3(std::array<int, 2>{44, 55});
+ EXPECT_TRUE(Equal(erasedVar->AsConcrete<std::array<int, 2>>(), std::array<int, 2>{11, 22}));
+
+ EXPECT_TRUE(Equal(NGlobal::GetErasedVariable(TestTag3)->AsConcrete<std::array<int, 2>>(), std::array<int, 2>{44, 55}));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/global/unittests/global_variable/odr_violation_avoidance_ut/ya.make b/library/cpp/yt/global/unittests/global_variable/odr_violation_avoidance_ut/ya.make
new file mode 100644
index 00000000000..9b409eb0d18
--- /dev/null
+++ b/library/cpp/yt/global/unittests/global_variable/odr_violation_avoidance_ut/ya.make
@@ -0,0 +1,16 @@
+GTEST(unittester-library-global-ord-violation-avoidence)
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+SRCS(
+ main.cpp
+)
+
+PEERDIR(
+ library/cpp/yt/global
+
+ library/cpp/yt/global/mock_modules/module3_public
+ library/cpp/yt/global/mock_modules/module3_defs
+)
+
+END()
diff --git a/library/cpp/yt/global/unittests/global_variable/two_modules_ut/main.cpp b/library/cpp/yt/global/unittests/global_variable/two_modules_ut/main.cpp
new file mode 100644
index 00000000000..1a31bcc81d9
--- /dev/null
+++ b/library/cpp/yt/global/unittests/global_variable/two_modules_ut/main.cpp
@@ -0,0 +1,90 @@
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <library/cpp/yt/global/access.h>
+
+#include <library/cpp/yt/global/mock_modules/module1_defs/direct_access.h>
+#include <library/cpp/yt/global/mock_modules/module1_public/test_tag.h>
+
+#include <library/cpp/yt/global/mock_modules/module2_defs/direct_access.h>
+#include <library/cpp/yt/global/mock_modules/module2_public/test_tag.h>
+
+#include <optional>
+#include <thread>
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// NB: Test is identical to just_works_ut.
+// We check that linking non-conflicting module has no interference.
+TEST(TGlobalVariableTest, JustWorks)
+{
+ auto erasedVar = NGlobal::GetErasedVariable(TestTag1);
+ EXPECT_TRUE(erasedVar.has_value());
+
+ auto concreteVar = erasedVar->AsConcrete<int>();
+ EXPECT_EQ(concreteVar, GetTestVariable1());
+
+ SetTestVariable1(12344);
+
+ // NB: We copied variable, not a reference to it!
+ EXPECT_EQ(concreteVar, erasedVar->AsConcrete<int>());
+
+ EXPECT_EQ(NGlobal::GetErasedVariable(TestTag1)->AsConcrete<int>(), 12344);
+}
+
+TEST(TGlobalVariableTest, MissingTag)
+{
+ static constexpr NGlobal::TVariableTag MissingTag = {};
+ EXPECT_FALSE(NGlobal::GetErasedVariable(MissingTag));
+}
+
+TEST(TGlobalVariableTest, ThreadLocal)
+{
+ auto ensureConstructed = [] {
+ // NB: tls variable is constructed only after
+ // being referred to for the first time.
+ auto val = GetTlsVariable();
+ ++val;
+ Y_UNUSED(val);
+ };
+
+ auto checkTls = [&ensureConstructed] (int val) {
+ ensureConstructed();
+
+ auto erasedVar = NGlobal::GetErasedVariable(ThreadLocalTag);
+ EXPECT_TRUE(erasedVar);
+ EXPECT_EQ(erasedVar->AsConcrete<int>(), 0);
+
+ EXPECT_EQ(GetTlsVariable(), 0);
+ SetTlsVariable(val);
+
+ EXPECT_EQ(erasedVar->AsConcrete<int>(), 0);
+ EXPECT_EQ(NGlobal::GetErasedVariable(ThreadLocalTag)->AsConcrete<int>(), val);
+ };
+
+ checkTls(42);
+
+ for (int idx = 0; idx < 42; ++idx) {
+ auto thread = std::thread(std::bind(checkTls, idx << 2));
+ thread.join();
+ }
+}
+
+TEST(TGlobalVariableTest, JustWorksAnotherModule)
+{
+ auto erasedVar = NGlobal::GetErasedVariable(TestTag2);
+ EXPECT_TRUE(erasedVar.has_value());
+
+ EXPECT_EQ(erasedVar->AsConcrete<TGuid>(), GetTestVariable2());
+
+ auto val = TGuid{1, 2, 3, 4};
+ SetTestVariable2(val);
+ EXPECT_EQ(NGlobal::GetErasedVariable(TestTag2)->AsConcrete<TGuid>(), val);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/global/unittests/global_variable/two_modules_ut/ya.make b/library/cpp/yt/global/unittests/global_variable/two_modules_ut/ya.make
new file mode 100644
index 00000000000..ae7757ab4dc
--- /dev/null
+++ b/library/cpp/yt/global/unittests/global_variable/two_modules_ut/ya.make
@@ -0,0 +1,20 @@
+GTEST(unittester-library-global-two_modules)
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+SRCS(
+ main.cpp
+)
+
+PEERDIR(
+ library/cpp/yt/global
+ library/cpp/yt/misc
+
+ library/cpp/yt/global/mock_modules/module1_public
+ library/cpp/yt/global/mock_modules/module1_defs
+
+ library/cpp/yt/global/mock_modules/module2_public
+ library/cpp/yt/global/mock_modules/module2_defs
+)
+
+END()
diff --git a/library/cpp/yt/global/variable-inl.h b/library/cpp/yt/global/variable-inl.h
new file mode 100644
index 00000000000..32d22fd7c84
--- /dev/null
+++ b/library/cpp/yt/global/variable-inl.h
@@ -0,0 +1,95 @@
+#ifndef GLOBAL_VARIABLE_INL_H_
+#error "Direct inclusion of this file is not allowed, include variable.h"
+// For the sake of sane code completion.
+#include "variable.h"
+#endif
+
+namespace NYT::NGlobal {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <CTriviallyErasable<GlobalVariableMaxByteSize> T>
+TVariable<T>::TVariable(
+ const TVariableTag& tag,
+ TAccessor accessor,
+ T initValue) noexcept
+ : Value_(initValue)
+{
+ NDetail::RegisterVariable(tag, accessor);
+}
+
+template <CTriviallyErasable<GlobalVariableMaxByteSize> T>
+T TVariable<T>::Get() const noexcept
+{
+ return Value_;
+}
+
+template <CTriviallyErasable<GlobalVariableMaxByteSize> T>
+void TVariable<T>::Set(T value) noexcept
+{
+ Value_ = value;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+#undef YT_DEFINE_TRACKED_GLOBAL
+#undef YT_DEFINE_TRACKED_THREAD_LOCAL
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define YT_DEFINE_TRACKED_GLOBAL(Type, Name, Tag, InitExpr) \
+ namespace NGlobalTracking##Name##Tag { \
+ \
+ ::NYT::NGlobal::TErasedStorage GetErased##Name() noexcept; \
+ \
+ static ::NYT::NGlobal::TVariable<Type> Name{Tag, GetErased##Name, (InitExpr)}; \
+ \
+ ::NYT::NGlobal::TErasedStorage GetErased##Name() noexcept \
+ { \
+ return ::NYT::NGlobal::TErasedStorage{Name.Get()}; \
+ } \
+ \
+ } /*namespace NGlobalTracking##Name##Tag*/ \
+ using NGlobalTracking##Name##Tag::Name; \
+ static_assert(true)
+
+// NB(arkady-e1ppa): We must ensure that tracker is constructed thus
+// we have to call ref tracker inside tls accessor.
+// NB(arkady-e1ppa): Unlike normal static variable, we cannot just pull
+// varibale name out as we might want to forward-declare thread local variable
+// now that it is modelled as function. Pulling alias from ns unfortunately
+// doesn't work as function definition :(.
+#define YT_DEFINE_TRACKED_THREAD_LOCAL(Type, Name, Tag, ...) \
+ Y_NO_INLINE Type& Name(); \
+ namespace NGlobalTracking##Name##Tag { \
+ \
+ void EnsureTracked() noexcept; \
+ ::NYT::NGlobal::TErasedStorage GetErased##Name() noexcept; \
+ \
+ static ::NYT::NGlobal::TVariable<std::byte> TlsTrackerFor##Name{Tag, GetErased##Name}; \
+ \
+ void EnsureTracked() noexcept \
+ { \
+ auto val = TlsTrackerFor##Name.Get(); \
+ Y_UNUSED(val); \
+ } \
+ \
+ ::NYT::NGlobal::TErasedStorage GetErased##Name() noexcept \
+ { \
+ return ::NYT::NGlobal::TErasedStorage{Name()}; \
+ } \
+ \
+ } /*namespace NGlobalTracking##Name##Tag*/ \
+ Y_NO_INLINE Type& Name() \
+ { \
+ thread_local Type tlsData { __VA_ARGS__ }; \
+ asm volatile(""); \
+ NGlobalTracking##Name##Tag::EnsureTracked(); \
+ return tlsData; \
+ } \
+ \
+ static_assert(true)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NGlobal
diff --git a/library/cpp/yt/global/variable.h b/library/cpp/yt/global/variable.h
new file mode 100644
index 00000000000..8b1a6c92a99
--- /dev/null
+++ b/library/cpp/yt/global/variable.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include "access.h"
+
+namespace NYT::NGlobal {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+// Defined in impl.cpp.
+void RegisterVariable(const TVariableTag& tag, TAccessor accessor);
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <CTriviallyErasable<GlobalVariableMaxByteSize> T>
+class TVariable
+{
+public:
+ TVariable(
+ const TVariableTag& tag,
+ TAccessor accessor,
+ T initValue = {}) noexcept;
+
+ TVariable(const TVariable& other) = delete;
+ TVariable& operator=(const TVariable& other) = delete;
+
+ T Get() const noexcept;
+ void Set(T value) noexcept;
+
+private:
+ // NB(arkady-e1ppa): Ban TVariable<TVariable<T>>.
+ static_assert(!requires (T t) {
+ [] <class U> (TVariable<U>&) { } (t);
+ });
+
+ T Value_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define YT_DEFINE_TRACKED_GLOBAL(Type, Name, Tag, InitExpr)
+#define YT_DEFINE_TRACKED_THREAD_LOCAL(Type, Name, Tag, ...)
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NGlobal
+
+#define GLOBAL_VARIABLE_INL_H_
+#include "variable-inl.h"
+#undef GLOBAL_VARIABLE_INL_H_
diff --git a/library/cpp/yt/global/ya.make b/library/cpp/yt/global/ya.make
new file mode 100644
index 00000000000..a423fb15390
--- /dev/null
+++ b/library/cpp/yt/global/ya.make
@@ -0,0 +1,23 @@
+LIBRARY()
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+SRCS(
+ impl.cpp
+)
+
+PEERDIR(
+ library/cpp/yt/assert
+ library/cpp/yt/misc
+ library/cpp/yt/memory
+ library/cpp/yt/threading
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ unittests/global_variable/just_works_ut
+ unittests/global_variable/missing_module_ut
+ unittests/global_variable/odr_violation_avoidance_ut
+ unittests/global_variable/two_modules_ut
+)
diff --git a/library/cpp/yt/memory/erased_storage-inl.h b/library/cpp/yt/memory/erased_storage-inl.h
index e4dc148e11e..8b6a303dffd 100644
--- a/library/cpp/yt/memory/erased_storage-inl.h
+++ b/library/cpp/yt/memory/erased_storage-inl.h
@@ -4,34 +4,45 @@
#include "erased_storage.h"
#endif
+#include <algorithm>
+
namespace NYT {
////////////////////////////////////////////////////////////////////////////////
-template <CTriviallyErasable TDecayedConcrete>
-TErasedStorage::TErasedStorage(TDecayedConcrete concrete) noexcept
+template <size_t MaxByteSize>
+template <CTriviallyErasable<MaxByteSize> TDecayedConcrete>
+TErasedStorage<MaxByteSize>::TErasedStorage(TDecayedConcrete concrete) noexcept
{
+ // NB(arkady-e1ppa): We want to be able to compare
+ // erased objects as if they are not erased.
+ // Assuming erased type's operator ==
+ // is equivalent to bitwise comparison.
+ std::ranges::fill(Bytes_, std::byte(0));
std::construct_at(
&AsConcrete<TDecayedConcrete>(),
concrete);
}
-template <CTriviallyErasable TDecayedConcrete>
-TDecayedConcrete& TErasedStorage::AsConcrete() & noexcept
+template <size_t MaxByteSize>
+template <CTriviallyErasable<MaxByteSize> TDecayedConcrete>
+TDecayedConcrete& TErasedStorage<MaxByteSize>::AsConcrete() & noexcept
{
using TPtr = TDecayedConcrete*;
return *std::launder(reinterpret_cast<TPtr>(&Bytes_));
}
-template <CTriviallyErasable TDecayedConcrete>
-const TDecayedConcrete& TErasedStorage::AsConcrete() const & noexcept
+template <size_t MaxByteSize>
+template <CTriviallyErasable<MaxByteSize> TDecayedConcrete>
+const TDecayedConcrete& TErasedStorage<MaxByteSize>::AsConcrete() const & noexcept
{
using TPtr = const TDecayedConcrete*;
return *std::launder(reinterpret_cast<TPtr>(&Bytes_));
}
-template <CTriviallyErasable TDecayedConcrete>
-TDecayedConcrete&& TErasedStorage::AsConcrete() && noexcept
+template <size_t MaxByteSize>
+template <CTriviallyErasable<MaxByteSize> TDecayedConcrete>
+TDecayedConcrete&& TErasedStorage<MaxByteSize>::AsConcrete() && noexcept
{
using TPtr = TDecayedConcrete*;
return std::move(*std::launder(reinterpret_cast<TPtr>(&Bytes_)));
diff --git a/library/cpp/yt/memory/erased_storage.h b/library/cpp/yt/memory/erased_storage.h
index 8e85430d0ba..46ea5cc854d 100644
--- a/library/cpp/yt/memory/erased_storage.h
+++ b/library/cpp/yt/memory/erased_storage.h
@@ -7,15 +7,28 @@ namespace NYT {
////////////////////////////////////////////////////////////////////////////////
-constexpr size_t ErasedStorageMaxByteSize = 32;
+template <size_t MaxByteSize>
+class TErasedStorage;
////////////////////////////////////////////////////////////////////////////////
-class TErasedStorage;
+namespace NDetail {
+
+template <class T>
+struct TIsErasedStorage
+ : public std::false_type
+{ };
+
+template <size_t N>
+struct TIsErasedStorage<TErasedStorage<N>>
+ : public std::true_type
+{ };
+
+} // namespace NDetail
////////////////////////////////////////////////////////////////////////////////
-template <class T>
+template <class T, size_t ErasedStorageMaxByteSize>
concept CTriviallyErasable =
std::default_initializable<T> &&
std::is_trivially_destructible_v<T> &&
@@ -23,33 +36,38 @@ concept CTriviallyErasable =
(sizeof(T) <= ErasedStorageMaxByteSize) &&
(alignof(T) <= ErasedStorageMaxByteSize) &&
!std::is_reference_v<T> &&
- !std::same_as<T, TErasedStorage>;
+ !NDetail::TIsErasedStorage<T>::value;
////////////////////////////////////////////////////////////////////////////////
// This class does not call dtor of erased object
// thus we require trivial destructability.
+template <size_t MaxByteSize>
class TErasedStorage
{
public:
- template <CTriviallyErasable TDecayedConcrete>
+ static constexpr size_t ByteSize = MaxByteSize;
+
+ template <CTriviallyErasable<MaxByteSize> TDecayedConcrete>
explicit TErasedStorage(TDecayedConcrete concrete) noexcept;
TErasedStorage(const TErasedStorage& other) = default;
TErasedStorage& operator=(const TErasedStorage& other) = default;
- template <CTriviallyErasable TDecayedConcrete>
+ template <CTriviallyErasable<MaxByteSize> TDecayedConcrete>
TDecayedConcrete& AsConcrete() & noexcept;
- template <CTriviallyErasable TDecayedConcrete>
+ template <CTriviallyErasable<MaxByteSize> TDecayedConcrete>
const TDecayedConcrete& AsConcrete() const & noexcept;
- template <CTriviallyErasable TDecayedConcrete>
+ template <CTriviallyErasable<MaxByteSize> TDecayedConcrete>
TDecayedConcrete&& AsConcrete() && noexcept;
+ bool operator==(const TErasedStorage& other) const = default;
+
private:
// NB(arkady-e1ppa): aligned_storage is deprecated.
- alignas(ErasedStorageMaxByteSize) std::byte Bytes_[ErasedStorageMaxByteSize];
+ alignas(MaxByteSize) std::byte Bytes_[MaxByteSize];
};
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/memory/unittests/erased_storage_ut.cpp b/library/cpp/yt/memory/unittests/erased_storage_ut.cpp
index 194c8e7fce5..5545423bcf3 100644
--- a/library/cpp/yt/memory/unittests/erased_storage_ut.cpp
+++ b/library/cpp/yt/memory/unittests/erased_storage_ut.cpp
@@ -46,6 +46,13 @@ private:
[[maybe_unused]] std::array<std::byte, 8> Data_;
};
+// Overshadow to bind template parameter.
+inline constexpr size_t TestSize = 32;
+
+template <class T>
+concept CTriviallyErasable = ::NYT::CTriviallyErasable<T, TestSize>;
+using TErasedStorage = ::NYT::TErasedStorage<TestSize>;
+
////////////////////////////////////////////////////////////////////////////////
TEST(TErasedStorageTest, Types)
@@ -124,6 +131,21 @@ TEST(TErasedStorageTest, MutateStorage)
EXPECT_EQ(stor.AsConcrete<int>(), 88);
}
+TEST(TErasedStorageTest, EqualityComparison)
+{
+ struct TWidget
+ {
+ alignas(8) int Value;
+
+ alignas(16) bool Flag;
+ } widget{1, false};
+
+ TErasedStorage stor1(widget);
+ TErasedStorage stor2(widget);
+
+ EXPECT_EQ(stor1, stor2);
+}
+
////////////////////////////////////////////////////////////////////////////////
} // namespace
diff --git a/library/cpp/yt/threading/public.h b/library/cpp/yt/threading/public.h
index ab6cb226633..5b02930c637 100644
--- a/library/cpp/yt/threading/public.h
+++ b/library/cpp/yt/threading/public.h
@@ -11,4 +11,9 @@ namespace NYT::NThreading {
////////////////////////////////////////////////////////////////////////////////
+using TThreadId = size_t;
+constexpr size_t InvalidThreadId = 0;
+
+////////////////////////////////////////////////////////////////////////////////
+
} // namespace NYT::NThreading