aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/yt/global
diff options
context:
space:
mode:
authorarkady-e1ppa <arkady-e1ppa@yandex-team.com>2024-08-15 18:32:55 +0300
committerarkady-e1ppa <arkady-e1ppa@yandex-team.com>2024-08-15 18:44:19 +0300
commit28ff4da78aa89f0226af33522332b7f06521412e (patch)
tree09c5bbbba83b4a04d51a77c654d0eaeab9bb404f /library/cpp/yt/global
parent0ecc758c681a1a8d54fbf95f0b58bba175518fac (diff)
downloadydb-28ff4da78aa89f0226af33522332b7f06521412e.tar.gz
YT-21233: Split error into error and stripped_error to rid the latter of deps on global variables
What happened: 1. error contents has been split into stripped_error and error. stripped_error contains the error itself (with attributes for now) and macros; error contains stripped_error and some extensions, namely, functions to get fiberId, hostname and traceid/spanid and all functions used to (de-)serialize error. This means that you cannot print error if you only include stripped_error, therefore you are likely to still require the entire error.h at the moment. 2. Mechanic for gathering origin attributes has been moved to newly created library/cpp/yt/error thus having no dependency on fibers, net or tracing. stripped_error uses these attributes as extendable semi-erased (meaning, you still would have to add a field and recompile the entire thing, but you don't have to introduce an extra dependency) storage for a bunch of attributes 3. Parsing of said attributes is done in error file (and not stripped_error). P.S. So far the plan is to eventually move stripped_error (once dependency on core/ytree/attributes is eliminated) without any actual change to dependency graph of anything outside of core (e.g. you would still have to include misc/error.h to use it). Next step would be re-teaching the error how to print, which would move some more methods from core to the standalone module. After that one could finally depend on the error itself and not the entire core. Annotations: [nodiff:caesar] 66615172181355821241d2e5f8e4a0f15e0ea791
Diffstat (limited to 'library/cpp/yt/global')
-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
28 files changed, 986 insertions, 0 deletions
diff --git a/library/cpp/yt/global/access.h b/library/cpp/yt/global/access.h
new file mode 100644
index 0000000000..e746f80c8b
--- /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 0000000000..033eedca34
--- /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 0000000000..abd536474c
--- /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 0000000000..78912be660
--- /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 0000000000..7152eb10b2
--- /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 0000000000..0b3ed55ebd
--- /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 0000000000..524e22b97e
--- /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 0000000000..d5f47c1be7
--- /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 0000000000..3db411bcb5
--- /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 0000000000..6692debb2f
--- /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 0000000000..3578368dcd
--- /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 0000000000..524e22b97e
--- /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 0000000000..3a2a4e24c4
--- /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 0000000000..a02fd15130
--- /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 0000000000..9fbc310488
--- /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 0000000000..a7e59f2762
--- /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 0000000000..524e22b97e
--- /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 0000000000..8618944404
--- /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 0000000000..570ab888dc
--- /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 0000000000..355bb64d78
--- /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 0000000000..f99a596455
--- /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 0000000000..d872763bf9
--- /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 0000000000..9b409eb0d1
--- /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 0000000000..1a31bcc81d
--- /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 0000000000..ae7757ab4d
--- /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 0000000000..32d22fd7c8
--- /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 0000000000..8b1a6c92a9
--- /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 0000000000..a423fb1539
--- /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
+)