aboutsummaryrefslogtreecommitdiffstats
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
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
-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
-rw-r--r--yt/yt/core/concurrency/fiber_scheduler_thread.cpp6
-rw-r--r--yt/yt/core/misc/async_slru_cache.h2
-rw-r--r--yt/yt/core/misc/error.cpp1443
-rw-r--r--yt/yt/core/misc/error.h483
-rw-r--r--yt/yt/core/misc/origin_attributes.cpp50
-rw-r--r--yt/yt/core/misc/origin_attributes.h23
-rw-r--r--yt/yt/core/misc/proc.h2
-rw-r--r--yt/yt/core/misc/stripped_error-inl.h (renamed from yt/yt/core/misc/error-inl.h)22
-rw-r--r--yt/yt/core/misc/stripped_error.cpp858
-rw-r--r--yt/yt/core/misc/stripped_error.h458
-rw-r--r--yt/yt/core/misc/unittests/error_ut.cpp98
-rw-r--r--yt/yt/core/net/local_address.cpp3
-rw-r--r--yt/yt/core/rpc/bus/channel.cpp4
-rw-r--r--yt/yt/core/threading/public.h5
-rw-r--r--yt/yt/core/tracing/trace_context.cpp11
-rw-r--r--yt/yt/core/ya.make4
51 files changed, 3156 insertions, 1605 deletions
diff --git a/library/cpp/yt/error/origin_attributes.cpp b/library/cpp/yt/error/origin_attributes.cpp
new file mode 100644
index 0000000000..5ff0b03933
--- /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 0000000000..05815c1e38
--- /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 0000000000..f5bd946199
--- /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 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
+)
diff --git a/library/cpp/yt/memory/erased_storage-inl.h b/library/cpp/yt/memory/erased_storage-inl.h
index e4dc148e11..8b6a303dff 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 8e85430d0b..46ea5cc854 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 194c8e7fce..5545423bcf 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 ab6cb22663..5b02930c63 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
diff --git a/yt/yt/core/concurrency/fiber_scheduler_thread.cpp b/yt/yt/core/concurrency/fiber_scheduler_thread.cpp
index 3c16120414..1add139c3f 100644
--- a/yt/yt/core/concurrency/fiber_scheduler_thread.cpp
+++ b/yt/yt/core/concurrency/fiber_scheduler_thread.cpp
@@ -16,6 +16,10 @@
#include <yt/yt/core/tracing/trace_context.h>
+#include <library/cpp/yt/error/origin_attributes.h>
+
+#include <library/cpp/yt/global/variable.h>
+
#include <library/cpp/yt/memory/memory_tag.h>
#include <library/cpp/yt/memory/function_view.h>
@@ -1087,11 +1091,13 @@ YT_DEFINE_THREAD_LOCAL(TFiberId, CurrentFiberId);
TFiberId GetCurrentFiberId()
{
+ NYT::NOrigin::EnableOriginOverrides();
return CurrentFiberId();
}
void SetCurrentFiberId(TFiberId id)
{
+ NYT::NOrigin::EnableOriginOverrides();
CurrentFiberId() = id;
}
diff --git a/yt/yt/core/misc/async_slru_cache.h b/yt/yt/core/misc/async_slru_cache.h
index bf251ec39f..b6ce9f3b98 100644
--- a/yt/yt/core/misc/async_slru_cache.h
+++ b/yt/yt/core/misc/async_slru_cache.h
@@ -6,8 +6,6 @@
#include <yt/yt/core/actions/future.h>
-#include <yt/yt/core/misc/error.h>
-
#include <yt/yt/core/profiling/timing.h>
#include <yt/yt/library/profiling/sensor.h>
diff --git a/yt/yt/core/misc/error.cpp b/yt/yt/core/misc/error.cpp
index fb0ee6f37f..c707ddc2e0 100644
--- a/yt/yt/core/misc/error.cpp
+++ b/yt/yt/core/misc/error.cpp
@@ -1,40 +1,27 @@
#include "error.h"
#include "serialize.h"
+#include "origin_attributes.h"
-#include <yt/yt/core/concurrency/scheduler.h>
+#include <yt/yt/core/concurrency/public.h>
-#include <yt/yt_proto/yt/core/misc/proto/error.pb.h>
-
-#include <yt/yt/core/actions/callback.h>
+#include <yt/yt/core/net/local_address.h>
#include <yt/yt/core/misc/protobuf_helpers.h>
-#include <yt/yt/core/misc/string_helpers.h>
-#include <yt/yt/core/misc/proc.h>
-
-#include <yt/yt/core/net/local_address.h>
#include <yt/yt/core/tracing/trace_context.h>
#include <yt/yt/core/yson/tokenizer.h>
#include <yt/yt/core/ytree/attributes.h>
-#include <yt/yt/core/ytree/convert.h>
#include <yt/yt/core/ytree/fluent.h>
-#include <yt/yt/core/net/address.h>
-
-#include <library/cpp/yt/exception/exception.h>
-
-#include <library/cpp/yt/misc/thread_name.h>
-#include <library/cpp/yt/misc/tls.h>
-
-#include <util/string/subst.h>
+#include <yt/yt_proto/yt/core/misc/proto/error.pb.h>
-#include <util/system/error.h>
-#include <util/system/thread.h>
+#include <library/cpp/yt/global/variable.h>
namespace NYT {
+using namespace NTracing;
using namespace NYTree;
using namespace NYson;
@@ -45,1144 +32,258 @@ using NYT::ToProto;
constexpr TStringBuf OriginalErrorDepthAttribute = "original_error_depth";
-constexpr TStringBuf ErrorMessageTruncatedSuffix = "...<message truncated>";
-
////////////////////////////////////////////////////////////////////////////////
-void TErrorCode::Save(TStreamSaveContext& context) const
-{
- NYT::Save(context, Value_);
-}
+namespace NOrigin {
-void TErrorCode::Load(TStreamLoadContext& context)
-{
- NYT::Load(context, Value_);
-}
+namespace {
-void FormatValue(TStringBuilderBase* builder, TErrorCode code, TStringBuf spec)
+struct TExtensionData
{
- FormatValue(builder, static_cast<int>(code), spec);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-YT_DEFINE_THREAD_LOCAL(bool, ErrorSanitizerEnabled, false);
-YT_DEFINE_THREAD_LOCAL(TInstant, ErrorSanitizerDatetimeOverride);
-YT_DEFINE_THREAD_LOCAL(TSharedRef, ErrorSanitizerLocalHostNameOverride);
+ NConcurrency::TFiberId Fid = NConcurrency::InvalidFiberId;
+ const char* HostName = nullptr;
+ TTraceId TraceId = InvalidTraceId;
+ TSpanId SpanId = InvalidSpanId;
+};
-TErrorSanitizerGuard::TErrorSanitizerGuard(TInstant datetimeOverride, TSharedRef localHostNameOverride)
- : SavedEnabled_(ErrorSanitizerEnabled())
- , SavedDatetimeOverride_(ErrorSanitizerDatetimeOverride())
- , SavedLocalHostNameOverride_(ErrorSanitizerLocalHostNameOverride())
+TOriginAttributes::TErasedExtensionData Encode(TExtensionData data)
{
- ErrorSanitizerEnabled() = true;
- ErrorSanitizerDatetimeOverride() = datetimeOverride;
- ErrorSanitizerLocalHostNameOverride() = std::move(localHostNameOverride);
+ return TOriginAttributes::TErasedExtensionData{data};
}
-TErrorSanitizerGuard::~TErrorSanitizerGuard()
+TExtensionData Decode(const TOriginAttributes::TErasedExtensionData& storage)
{
- YT_ASSERT(ErrorSanitizerEnabled());
-
- ErrorSanitizerEnabled() = SavedEnabled_;
- ErrorSanitizerDatetimeOverride() = SavedDatetimeOverride_;
- ErrorSanitizerLocalHostNameOverride() = std::move(SavedLocalHostNameOverride_);
+ return storage.AsConcrete<TExtensionData>();
}
-////////////////////////////////////////////////////////////////////////////////
-
-class TError::TImpl
+void TryExtractHost(const TOriginAttributes& attributes)
{
-public:
- TImpl()
- : Code_(NYT::EErrorCode::OK)
- { }
-
- TImpl(const TError::TImpl& other)
- : Code_(other.Code_)
- , Message_(other.Message_)
- , Host_(other.Host_)
- , HostHolder_(other.HostHolder_)
- , Datetime_(other.Datetime_)
- , Pid_(other.Pid_)
- , Tid_(other.Tid_)
- , ThreadName_(other.ThreadName_)
- , Fid_(other.Fid_)
- , TraceId_(other.TraceId_)
- , SpanId_(other.SpanId_)
- , Attributes_(other.Attributes_ ? other.Attributes_->Clone() : nullptr)
- , InnerErrors_(other.InnerErrors_)
- { }
-
- explicit TImpl(TString message)
- : Code_(NYT::EErrorCode::Generic)
- , Message_(std::move(message))
- {
- CaptureOriginAttributes();
- }
-
- TImpl(TErrorCode code, TString message)
- : Code_(code)
- , Message_(std::move(message))
- {
- if (!IsOK()) {
- CaptureOriginAttributes();
- }
- }
-
- TErrorCode GetCode() const
- {
- return Code_;
- }
-
- void SetCode(TErrorCode code)
- {
- Code_ = code;
- }
-
- const TString& GetMessage() const
- {
- return Message_;
- }
-
- void SetMessage(TString message)
- {
- Message_ = std::move(message);
- }
-
- TString* MutableMessage()
- {
- return &Message_;
- }
-
- bool HasHost() const
- {
- return Host_.operator bool();
- }
-
- TStringBuf GetHost() const
- {
- return Host_;
- }
-
- bool HasOriginAttributes() const
- {
- return ThreadName_.Length > 0;
- }
-
- bool HasDatetime() const
- {
- return Datetime_ != TInstant();
- }
-
- TInstant GetDatetime() const
- {
- return Datetime_;
- }
-
- void SetDatetime(TInstant datetime)
- {
- Datetime_ = datetime;
- }
-
- TProcessId GetPid() const
- {
- return Pid_;
- }
-
- NThreading::TThreadId GetTid() const
- {
- return Tid_;
- }
-
- TStringBuf GetThreadName() const
- {
- return ThreadName_.ToStringBuf();
- }
-
- NConcurrency::TFiberId GetFid() const
- {
- return Fid_;
- }
-
- bool HasTracingAttributes() const
- {
- return TraceId_ != NTracing::InvalidTraceId;
- }
-
- void SetTracingAttributes(NTracing::TTracingAttributes tracingAttributes)
- {
- YT_VERIFY(!HasTracingAttributes());
-
- TraceId_ = tracingAttributes.TraceId;
- SpanId_ = tracingAttributes.SpanId;
- }
-
- NTracing::TTraceId GetTraceId() const
- {
- return TraceId_;
- }
-
- NTracing::TSpanId GetSpanId() const
- {
- return SpanId_;
- }
-
- const IAttributeDictionary& Attributes() const
- {
- if (!Attributes_) {
- return EmptyAttributes();
- }
- return *Attributes_;
- }
-
- IAttributeDictionary* MutableAttributes()
- {
- if (!Attributes_) {
- Attributes_ = CreateEphemeralAttributes();
- }
- return Attributes_.Get();
- }
-
- bool HasAttributes() const
- {
- return Attributes_.operator bool();
- }
-
- void SetAttributes(NYTree::IAttributeDictionaryPtr attributes)
- {
- Attributes_ = std::move(attributes);
- ExtractSystemAttributes();
- }
-
- const std::vector<TError>& InnerErrors() const
- {
- return InnerErrors_;
- }
-
- std::vector<TError>* MutableInnerErrors()
- {
- return &InnerErrors_;
- }
-
- bool IsOK() const
- {
- return Code_ == NYT::EErrorCode::OK;
- }
-
- void CopyBuiltinAttributesFrom(const TError::TImpl& other)
- {
- Host_ = other.Host_;
- HostHolder_ = other.HostHolder_;
- Datetime_ = other.Datetime_;
- Pid_ = other.Pid_;
- Tid_ = other.Tid_;
- ThreadName_ = other.ThreadName_;
- Fid_ = other.Fid_;
- TraceId_ = other.TraceId_;
- SpanId_ = other.SpanId_;
- }
-
-private:
- TErrorCode Code_;
- TString Message_;
- // Most errors are local; for these Host_ refers to a static buffer and HostHolder_ is not used.
- // This saves one allocation on TError construction.
- TStringBuf Host_;
- // HostHolder_ optionally stores data of Host_, and this pointer connection survives move of containing object.
- TSharedRef HostHolder_;
- TInstant Datetime_;
- TProcessId Pid_ = 0;
- NThreading::TThreadId Tid_ = NThreading::InvalidThreadId;
- TThreadName ThreadName_;
- NConcurrency::TFiberId Fid_ = NConcurrency::InvalidFiberId;
- NTracing::TTraceId TraceId_ = NTracing::InvalidTraceId;
- NTracing::TSpanId SpanId_ = NTracing::InvalidSpanId;
- NYTree::IAttributeDictionaryPtr Attributes_;
- std::vector<TError> InnerErrors_;
-
-
- void CaptureOriginAttributes()
- {
- if (ErrorSanitizerEnabled()) {
- Datetime_ = ErrorSanitizerDatetimeOverride();
- HostHolder_ = ErrorSanitizerLocalHostNameOverride();
- Host_ = HostHolder_.empty() ? TStringBuf() : TStringBuf(HostHolder_.Begin(), HostHolder_.End());
- return;
- }
-
- Host_ = NNet::ReadLocalHostName();
- Datetime_ = TInstant::Now();
- Pid_ = GetPID();
- Tid_ = TThread::CurrentThreadId();
- ThreadName_ = GetCurrentThreadName();
- Fid_ = NConcurrency::GetCurrentFiberId();
- if (const auto* traceContext = NTracing::TryGetCurrentTraceContext()) {
- TraceId_ = traceContext->GetTraceId();
- SpanId_ = traceContext->GetSpanId();
- }
- }
-
- void ExtractSystemAttributes()
- {
- if (!Attributes_) {
- return;
- }
-
- static const TString HostKey("host");
- HostHolder_ = TSharedRef::FromString(Attributes_->GetAndRemove<TString>(HostKey, TString()));
- Host_ = HostHolder_.empty() ? TStringBuf() : TStringBuf(HostHolder_.Begin(), HostHolder_.End());
-
- static const TString DatetimeKey("datetime");
- Datetime_ = Attributes_->GetAndRemove<TInstant>(DatetimeKey, TInstant());
-
- static const TString PidKey("pid");
- Pid_ = Attributes_->GetAndRemove<TProcessId>(PidKey, 0);
-
- static const TString TidKey("tid");
- Tid_ = Attributes_->GetAndRemove<NThreading::TThreadId>(TidKey, NThreading::InvalidThreadId);
-
- static const TString ThreadNameKey("thread");
- ThreadName_ = Attributes_->GetAndRemove<TString>(ThreadNameKey, TString());
-
- static const TString FidKey("fid");
- Fid_ = Attributes_->GetAndRemove<NConcurrency::TFiberId>(FidKey, NConcurrency::InvalidFiberId);
-
- static const TString TraceIdKey("trace_id");
- TraceId_ = Attributes_->GetAndRemove<NTracing::TTraceId>(TraceIdKey, NTracing::InvalidTraceId);
-
- static const TString SpanIdKey("span_id");
- SpanId_ = Attributes_->GetAndRemove<NTracing::TSpanId>(SpanIdKey, NTracing::InvalidSpanId);
- }
-};
-
-////////////////////////////////////////////////////////////////////////////////
-
-namespace {
-
-bool IsWhitelisted(const TError& error, const THashSet<TStringBuf>& attributeWhitelist)
-{
- for (const auto& key : error.Attributes().ListKeys()) {
- if (attributeWhitelist.contains(key)) {
- return true;
- }
- }
-
- for (const auto& innerError : error.InnerErrors()) {
- if (IsWhitelisted(innerError, attributeWhitelist)) {
- return true;
- }
- }
-
- return false;
-}
-
-//! Returns vector which consists of objects from errors such that:
-//! if N is the number of objects in errors s.t. IsWhitelisted is true
-//! then first N objects of returned vector are the ones for which IsWhitelisted is true
-//! followed by std::max(0, maxInnerErrorCount - N - 1) remaining objects
-//! finally followed by errors.back().
-std::vector<TError>& ApplyWhitelist(std::vector<TError>& errors, const THashSet<TStringBuf>& attributeWhitelist, int maxInnerErrorCount)
-{
- if (std::ssize(errors) < std::max(2, maxInnerErrorCount)) {
- return errors;
+ if (attributes.Host || !attributes.ExtensionData) {
+ return;
}
- auto firstNotWhitelisted = std::partition(
- errors.begin(),
- std::prev(errors.end()),
- [&attributeWhitelist] (const TError& error) {
- return IsWhitelisted(error, attributeWhitelist);
- });
-
- int lastErrorOffset = std::max<int>(firstNotWhitelisted - errors.begin(), maxInnerErrorCount - 1);
+ auto [
+ fid,
+ name,
+ traceId,
+ spanId
+ ] = Decode(*attributes.ExtensionData);
- *(errors.begin() + lastErrorOffset) = std::move(errors.back());
- errors.resize(lastErrorOffset + 1);
-
- return errors;
+ attributes.Host = name;
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
-TError::TErrorOr() = default;
-
-TError::~TErrorOr() = default;
-
-TError::TErrorOr(const TError& other)
-{
- if (!other.IsOK()) {
- Impl_ = std::make_unique<TImpl>(*other.Impl_);
- }
-}
-
-TError::TErrorOr(TError&& other) noexcept
- : Impl_(std::move(other.Impl_))
-{ }
-
-TError::TErrorOr(const std::exception& ex)
-{
- if (auto simpleException = dynamic_cast<const TSimpleException*>(&ex)) {
- *this = TError(NYT::EErrorCode::Generic, TRuntimeFormat(simpleException->GetMessage()));
- // NB: clang-14 is incapable of capturing structured binding variables
- // so we force materialize them via this function call.
- auto addAttribute = [this] (const auto& key, const auto& value) {
- std::visit([&] (const auto& actual) {
- *this <<= TErrorAttribute(key, actual);
- }, value);
- };
- for (const auto& [key, value] : simpleException->GetAttributes()) {
- addAttribute(key, value);
- }
- try {
- if (simpleException->GetInnerException()) {
- std::rethrow_exception(simpleException->GetInnerException());
- }
- } catch (const std::exception& innerEx) {
- *this <<= TError(innerEx);
- }
- } else if (const auto* errorEx = dynamic_cast<const TErrorException*>(&ex)) {
- *this = errorEx->Error();
- } else {
- *this = TError(NYT::EErrorCode::Generic, TRuntimeFormat(ex.what()));
- }
- YT_VERIFY(!IsOK());
-}
-
-TError::TErrorOr(TString message, TDisableFormat)
- : Impl_(std::make_unique<TImpl>(std::move(message)))
-{ }
-
-TError::TErrorOr(TErrorCode code, TString message, TDisableFormat)
- : Impl_(std::make_unique<TImpl>(code, std::move(message)))
-{ }
-
-TError& TError::operator = (const TError& other)
-{
- if (other.IsOK()) {
- Impl_.reset();
- } else {
- Impl_ = std::make_unique<TImpl>(*other.Impl_);
- }
- return *this;
-}
-
-TError& TError::operator = (TError&& other) noexcept
+bool HasHost(const TOriginAttributes& attributes) noexcept
{
- Impl_ = std::move(other.Impl_);
- return *this;
+ TryExtractHost(attributes);
+ return attributes.Host.operator bool();
}
-TError TError::FromSystem()
+TStringBuf GetHost(const TOriginAttributes& attributes) noexcept
{
- return FromSystem(LastSystemError());
+ TryExtractHost(attributes);
+ return attributes.Host;
}
-TError TError::FromSystem(int error)
+NConcurrency::TFiberId GetFid(const TOriginAttributes& attributes) noexcept
{
- return TError(TErrorCode(LinuxErrorCodeBase + error), TRuntimeFormat(LastSystemErrorText(error))) <<
- TErrorAttribute("errno", error);
-}
-
-TError TError::FromSystem(const TSystemError& error)
-{
- return FromSystem(error.Status());
-}
-
-TErrorCode TError::GetCode() const
-{
- if (!Impl_) {
- return NYT::EErrorCode::OK;
- }
- return Impl_->GetCode();
-}
-
-TError& TError::SetCode(TErrorCode code)
-{
- MakeMutable();
- Impl_->SetCode(code);
- return *this;
-}
-
-TErrorCode TError::GetNonTrivialCode() const
-{
- if (!Impl_) {
- return NYT::EErrorCode::OK;
- }
-
- if (GetCode() != NYT::EErrorCode::Generic) {
- return GetCode();
- }
-
- for (const auto& innerError : InnerErrors()) {
- auto innerCode = innerError.GetNonTrivialCode();
- if (innerCode != NYT::EErrorCode::Generic) {
- return innerCode;
- }
- }
-
- return GetCode();
-}
-
-THashSet<TErrorCode> TError::GetDistinctNonTrivialErrorCodes() const
-{
- THashSet<TErrorCode> result;
- TraverseError(*this, [&result] (const TError& error, int /*depth*/) {
- if (auto errorCode = error.GetCode(); errorCode != NYT::EErrorCode::OK) {
- result.insert(errorCode);
- }
- });
- return result;
-}
-
-const TString& TError::GetMessage() const
-{
- if (!Impl_) {
- static const TString Result;
- return Result;
- }
- return Impl_->GetMessage();
-}
-
-TError& TError::SetMessage(TString message)
-{
- MakeMutable();
- Impl_->SetMessage(std::move(message));
- return *this;
-}
-
-bool TError::HasHost() const
-{
- if (!Impl_) {
- return false;
- }
- return Impl_->HasHost();
-}
-
-TStringBuf TError::GetHost() const
-{
- if (!Impl_) {
- return {};
- }
- return Impl_->GetHost();
-}
-
-bool TError::HasOriginAttributes() const
-{
- if (!Impl_) {
- return false;
+ if (attributes.ExtensionData.has_value()) {
+ return Decode(*attributes.ExtensionData).Fid;
}
- return Impl_->HasOriginAttributes();
+ return NConcurrency::InvalidFiberId;
}
-bool TError::HasDatetime() const
+NTracing::TTraceId GetTraceId(const TOriginAttributes& attributes) noexcept
{
- if (!Impl_) {
- return false;
+ if (attributes.ExtensionData.has_value()) {
+ return Decode(*attributes.ExtensionData).TraceId;
}
- return Impl_->HasDatetime();
+ return InvalidTraceId;
}
-TInstant TError::GetDatetime() const
+NTracing::TSpanId GetSpanId(const TOriginAttributes& attributes) noexcept
{
- if (!Impl_) {
- return {};
+ if (attributes.ExtensionData.has_value()) {
+ return Decode(*attributes.ExtensionData).SpanId;
}
- return Impl_->GetDatetime();
+ return InvalidSpanId;
}
-TProcessId TError::GetPid() const
+bool HasTracingAttributes(const TOriginAttributes& attributes) noexcept
{
- if (!Impl_) {
- return 0;
- }
- return Impl_->GetPid();
-}
-
-NThreading::TThreadId TError::GetTid() const
-{
- if (!Impl_) {
- return NThreading::InvalidThreadId;
- }
- return Impl_->GetTid();
+ return GetTraceId(attributes) != InvalidTraceId;
}
-TStringBuf TError::GetThreadName() const
+void UpdateTracingAttributes(TOriginAttributes* attributes, const NTracing::TTracingAttributes& tracingAttributes)
{
- if (!Impl_) {
- static TString empty;
- return empty;
+ if (attributes->ExtensionData.has_value()) {
+ auto ext = Decode(*attributes->ExtensionData);
+ attributes->ExtensionData.emplace(Encode(TExtensionData{
+ .Fid = ext.Fid,
+ .HostName = ext.HostName,
+ .TraceId = tracingAttributes.TraceId,
+ .SpanId = tracingAttributes.SpanId,
+ }));
}
- return Impl_->GetThreadName();
-}
-
-NConcurrency::TFiberId TError::GetFid() const
-{
- if (!Impl_) {
- return NConcurrency::InvalidFiberId;
- }
- return Impl_->GetFid();
-}
-
-bool TError::HasTracingAttributes() const
-{
- if (!Impl_) {
- return false;
- }
- return Impl_->HasTracingAttributes();
-}
-void TError::SetTracingAttributes(NTracing::TTracingAttributes tracingAttributes)
-{
- if (!Impl_) {
- return;
- }
- Impl_->SetTracingAttributes(tracingAttributes);
+ attributes->ExtensionData.emplace(Encode(TExtensionData{
+ .TraceId = tracingAttributes.TraceId,
+ .SpanId = tracingAttributes.SpanId,
+ }));
}
-NTracing::TTraceId TError::GetTraceId() const
-{
- if (!Impl_) {
- return NTracing::InvalidTraceId;
- }
- return Impl_->GetTraceId();
-}
+////////////////////////////////////////////////////////////////////////////////
-NTracing::TSpanId TError::GetSpanId() const
+TOriginAttributes::TErasedExtensionData GetExtensionDataOverride()
{
- if (!Impl_) {
- return NTracing::InvalidSpanId;
- }
- return Impl_->GetSpanId();
-}
+ TExtensionData result;
+ result.Fid = NConcurrency::GetCurrentFiberId();
+ result.HostName = NNet::ReadLocalHostName();
-const IAttributeDictionary& TError::Attributes() const
-{
- if (!Impl_) {
- return EmptyAttributes();
+ if (auto* traceContext = NTracing::TryGetCurrentTraceContext()) {
+ result.TraceId = traceContext->GetTraceId();
+ result.SpanId = traceContext->GetSpanId();
}
- return Impl_->Attributes();
-}
-IAttributeDictionary* TError::MutableAttributes()
-{
- MakeMutable();
- return Impl_->MutableAttributes();
+ return TOriginAttributes::TErasedExtensionData{result};
}
-const std::vector<TError>& TError::InnerErrors() const
+TString FormatOriginOverride(const TOriginAttributes& attributes)
{
- if (!Impl_) {
- static const std::vector<TError> Result;
- return Result;
- }
- return Impl_->InnerErrors();
-}
-
-std::vector<TError>* TError::MutableInnerErrors()
-{
- MakeMutable();
- return Impl_->MutableInnerErrors();
-}
-
-const TString InnerErrorsTruncatedKey("inner_errors_truncated");
-
-TError TError::Truncate(
- int maxInnerErrorCount,
- i64 stringLimit,
- const THashSet<TStringBuf>& attributeWhitelist) const &
-{
- if (!Impl_) {
- return TError();
- }
-
- auto truncateInnerError = [=, &attributeWhitelist] (const TError& innerError) {
- return innerError.Truncate(maxInnerErrorCount, stringLimit, attributeWhitelist);
- };
-
- auto truncateAttributes = [stringLimit, &attributeWhitelist] (const IAttributeDictionary& attributes) {
- auto truncatedAttributes = CreateEphemeralAttributes();
- for (const auto& key : attributes.ListKeys()) {
- const auto& value = attributes.FindYson(key);
-
- if (std::ssize(value.AsStringBuf()) > stringLimit && !attributeWhitelist.contains(key)) {
- truncatedAttributes->SetYson(
- key,
- BuildYsonStringFluently()
- .Value("...<attribute truncated>..."));
- } else {
- truncatedAttributes->SetYson(
- key,
- value);
+ TryExtractHost(attributes);
+ return Format("%v (pid %v, thread %v, fid %x)",
+ attributes.Host,
+ attributes.Pid,
+ MakeFormatterWrapper([&] (auto* builder) {
+ auto threadName = attributes.ThreadName.ToStringBuf();
+ if (threadName.empty()) {
+ FormatValue(builder, attributes.Tid, "v");
+ return;
}
- }
- return truncatedAttributes;
- };
-
- auto result = std::make_unique<TImpl>();
- result->SetCode(GetCode());
- result->SetMessage(TruncateString(GetMessage(), stringLimit, ErrorMessageTruncatedSuffix));
- if (Impl_->HasAttributes()) {
- result->SetAttributes(truncateAttributes(Impl_->Attributes()));
- }
- result->CopyBuiltinAttributesFrom(*Impl_);
-
- const auto& innerErrors = InnerErrors();
- auto& copiedInnerErrors = *result->MutableInnerErrors();
-
- if (std::ssize(innerErrors) <= maxInnerErrorCount) {
- for (const auto& innerError : innerErrors) {
- copiedInnerErrors.push_back(truncateInnerError(innerError));
- }
- } else {
- result->MutableAttributes()->Set(InnerErrorsTruncatedKey, true);
-
- // NB(arkady-e1ppa): We want to always keep the last inner error,
- // so we make room for it and do not check if it is whitelisted.
- for (int idx = 0; idx < std::ssize(innerErrors) - 1; ++idx) {
- const auto& innerError = innerErrors[idx];
- if (
- IsWhitelisted(innerError, attributeWhitelist) ||
- std::ssize(copiedInnerErrors) < maxInnerErrorCount - 1)
- {
- copiedInnerErrors.push_back(truncateInnerError(innerError));
- }
- }
- copiedInnerErrors.push_back(truncateInnerError(innerErrors.back()));
- }
-
- return TError(std::move(result));
+ FormatValue(builder, threadName, "v");
+ }),
+ GetFid(attributes));
}
-TError TError::Truncate(
- int maxInnerErrorCount,
- i64 stringLimit,
- const THashSet<TStringBuf>& attributeWhitelist) &&
+TOriginAttributes ExtractFromDictionaryOverride(const NYTree::IAttributeDictionaryPtr& attributes)
{
- if (!Impl_) {
- return TError();
- }
+ auto result = NYT::NDetail::ExtractFromDictionaryDefault(attributes);
- auto truncateInnerError = [=, &attributeWhitelist] (TError& innerError) {
- innerError = std::move(innerError).Truncate(maxInnerErrorCount, stringLimit, attributeWhitelist);
- };
+ TExtensionData ext;
- auto truncateAttributes = [stringLimit, &attributeWhitelist] (IAttributeDictionary* attributes) {
- for (const auto& key : attributes->ListKeys()) {
- if (std::ssize(attributes->FindYson(key).AsStringBuf()) > stringLimit && !attributeWhitelist.contains(key)) {
- attributes->SetYson(
- key,
- BuildYsonStringFluently()
- .Value("...<attribute truncated>..."));
- }
- }
- };
+ static const TString FidKey("fid");
+ ext.Fid = attributes->GetAndRemove<NConcurrency::TFiberId>(FidKey, NConcurrency::InvalidFiberId);
- TruncateStringInplace(Impl_->MutableMessage(), stringLimit, ErrorMessageTruncatedSuffix);
- if (Impl_->HasAttributes()) {
- truncateAttributes(Impl_->MutableAttributes());
- }
- if (std::ssize(InnerErrors()) <= maxInnerErrorCount) {
- for (auto& innerError : *MutableInnerErrors()) {
- truncateInnerError(innerError);
- }
- } else {
- auto& innerErrors = ApplyWhitelist(*MutableInnerErrors(), attributeWhitelist, maxInnerErrorCount);
- MutableAttributes()->Set(InnerErrorsTruncatedKey, true);
+ static const TString TraceIdKey("trace_id");
+ ext.TraceId = attributes->GetAndRemove<NTracing::TTraceId>(TraceIdKey, NTracing::InvalidTraceId);
- for (auto& innerError : innerErrors) {
- truncateInnerError(innerError);
- }
- }
+ static const TString SpanIdKey("span_id");
+ ext.SpanId = attributes->GetAndRemove<NTracing::TSpanId>(SpanIdKey, NTracing::InvalidSpanId);
- return std::move(*this);
+ result.ExtensionData = Encode(ext);
+ return result;
}
-bool TError::IsOK() const
-{
- if (!Impl_) {
- return true;
- }
- return Impl_->IsOK();
-}
+////////////////////////////////////////////////////////////////////////////////
-TError TError::Wrap() const &
+void EnableOriginOverrides()
{
- return *this;
-}
+ static NGlobal::TVariable<std::byte> getExtensionDataOverride{
+ NYT::NDetail::GetExtensionDataTag,
+ +[] () noexcept {
+ return NGlobal::TErasedStorage{&GetExtensionDataOverride};
+ }};
-TError TError::Wrap() &&
-{
- return std::move(*this);
-}
+ static NGlobal::TVariable<std::byte> formatOriginOverride{
+ NYT::NDetail::FormatOriginTag,
+ +[] () noexcept {
+ return NGlobal::TErasedStorage{&FormatOriginOverride};
+ }};
-Y_WEAK TString GetErrorSkeleton(const TError& /*error*/)
-{
- // Proper implementation resides in yt/yt/library/error_skeleton/skeleton.cpp.
- THROW_ERROR_EXCEPTION("Error skeleton implementation library is not linked; consider PEERDIR'ing yt/yt/library/error_skeleton");
-}
+ static NGlobal::TVariable<std::byte> extractFromDictionaryOverride{
+ NYT::NDetail::ExtractFromDictionaryTag,
+ +[] () noexcept {
+ return NGlobal::TErasedStorage{&ExtractFromDictionaryOverride};
+ }};
-TString TError::GetSkeleton() const
-{
- return GetErrorSkeleton(*this);
+ getExtensionDataOverride.Get();
+ formatOriginOverride.Get();
+ extractFromDictionaryOverride.Get();
}
-void TError::Save(TStreamSaveContext& context) const
-{
- using NYT::Save;
-
- if (!Impl_) {
- // Fast path.
- Save(context, TErrorCode(NYT::EErrorCode::OK)); // code
- Save(context, TStringBuf()); // message
- Save(context, IAttributeDictionaryPtr()); // attributes
- Save(context, std::vector<TError>()); // inner errors
- return;
- }
-
- Save(context, GetCode());
- Save(context, GetMessage());
-
- // Cf. TAttributeDictionaryValueSerializer.
- auto attributePairs = Attributes().ListPairs();
- size_t attributeCount = attributePairs.size();
- if (HasOriginAttributes()) {
- attributeCount += 5;
- }
- if (HasDatetime()) {
- attributeCount += 1;
- }
- if (HasTracingAttributes()) {
- attributeCount += 2;
- }
-
- if (attributeCount > 0) {
- // Cf. TAttributeDictionaryRefSerializer.
- Save(context, true);
-
- TSizeSerializer::Save(context, attributeCount);
-
- auto saveAttribute = [&] (const TString& key, const auto& value) {
- Save(context, key);
- Save(context, ConvertToYsonString(value));
- };
-
- if (HasOriginAttributes()) {
- static const TString HostKey("host");
- saveAttribute(HostKey, GetHost());
-
- static const TString PidKey("pid");
- saveAttribute(PidKey, GetPid());
-
- static const TString TidKey("tid");
- saveAttribute(TidKey, GetTid());
-
- static const TString ThreadNameKey("thread");
- saveAttribute(ThreadNameKey, GetThreadName());
-
- static const TString FidKey("fid");
- saveAttribute(FidKey, GetFid());
- }
-
- if (HasDatetime()) {
- static const TString DatetimeKey("datetime");
- saveAttribute(DatetimeKey, GetDatetime());
- }
-
- if (HasTracingAttributes()) {
- static const TString TraceIdKey("trace_id");
- saveAttribute(TraceIdKey, GetTraceId());
-
- static const TString SpanIdKey("span_id");
- saveAttribute(SpanIdKey, GetSpanId());
- }
-
- std::sort(attributePairs.begin(), attributePairs.end(), [] (const auto& lhs, const auto& rhs) {
- return lhs.first < rhs.first;
- });
- for (const auto& [key, value] : attributePairs) {
- Save(context, key);
- Save(context, value);
- }
- } else {
- Save(context, false);
- }
+} // namespace NOrigin
- Save(context, InnerErrors());
-}
+////////////////////////////////////////////////////////////////////////////////
-void TError::Load(TStreamLoadContext& context)
+bool HasHost(const TError& error) noexcept
{
- Impl_.reset();
-
- using NYT::Load;
-
- auto code = Load<TErrorCode>(context);
- auto message = Load<TString>(context);
-
- IAttributeDictionaryPtr attributes;
- if (Load<bool>(context)) {
- attributes = CreateEphemeralAttributes();
- TAttributeDictionarySerializer::LoadNonNull(context, attributes);
+ if (auto* attributes = error.MutableOriginAttributes()) {
+ return NOrigin::HasHost(*attributes);
}
-
- auto innerErrors = Load<std::vector<TError>>(context);
-
- if (code == NYT::EErrorCode::OK) {
- // Fast path.
- // Note that there were no allocations above.
- return;
- }
-
- auto impl = std::make_unique<TImpl>();
- impl->SetCode(code);
- impl->SetMessage(std::move(message));
- impl->SetAttributes(std::move(attributes));
- *impl->MutableInnerErrors() = std::move(innerErrors);
- Impl_ = std::move(impl);
-}
-
-std::optional<TError> TError::FindMatching(TErrorCode code) const
-{
- return FindMatching([&] (TErrorCode errorCode) {
- return code == errorCode;
- });
+ return false;
}
-std::optional<TError> TError::FindMatching(const THashSet<TErrorCode>& codes) const
+TStringBuf GetHost(const TError& error) noexcept
{
- return FindMatching([&] (TErrorCode code) {
- return codes.contains(code);
- });
-}
-
-TError::TErrorOr(std::unique_ptr<TImpl> impl)
- : Impl_(std::move(impl))
-{ }
-
-void TError::MakeMutable()
-{
- if (!Impl_) {
- Impl_ = std::make_unique<TImpl>();
+ if (auto* attributes = error.MutableOriginAttributes()) {
+ return NOrigin::GetHost(*attributes);
}
+ return {};
}
-////////////////////////////////////////////////////////////////////////////////
-
-namespace {
-
-void AppendIndent(TStringBuilderBase* builer, int indent)
-{
- builer->AppendChar(' ', indent);
-}
-
-void AppendAttribute(TStringBuilderBase* builder, const TString& key, const TString& value, int indent)
+NConcurrency::TFiberId GetFid(const TError& error) noexcept
{
- AppendIndent(builder, indent + 4);
- if (!value.Contains('\n')) {
- builder->AppendFormat("%-15s %s", key, value);
- } else {
- builder->AppendString(key);
- TString indentedValue = "\n" + value;
- SubstGlobal(indentedValue, "\n", "\n" + TString{static_cast<size_t>(indent + 8), ' '});
- // Now first line in indentedValue is empty and every other line is indented by 8 spaces.
- builder->AppendString(indentedValue);
+ if (auto* attributes = error.MutableOriginAttributes()) {
+ return NOrigin::GetFid(*attributes);
}
- builder->AppendChar('\n');
+ return NConcurrency::InvalidFiberId;
}
-void AppendError(TStringBuilderBase* builder, const TError& error, int indent)
+bool HasTracingAttributes(const TError& error) noexcept
{
- if (error.IsOK()) {
- builder->AppendString("OK");
- return;
- }
-
- AppendIndent(builder, indent);
- builder->AppendString(error.GetMessage());
- builder->AppendChar('\n');
-
- if (error.GetCode() != NYT::EErrorCode::Generic) {
- AppendAttribute(builder, "code", ToString(static_cast<int>(error.GetCode())), indent);
- }
-
- // Pretty-print origin.
- if (error.HasOriginAttributes()) {
- AppendAttribute(
- builder,
- "origin",
- Format("%v (pid %v, thread %v, fid %x)",
- error.GetHost(),
- error.GetPid(),
- (!error.GetThreadName().empty() ? error.GetThreadName() : ToString(error.GetTid())),
- error.GetFid()),
- indent);
- } else if (ErrorSanitizerEnabled() && error.HasHost()) {
- AppendAttribute(
- builder,
- "host",
- ToString(error.GetHost()),
- indent);
- }
-
- if (error.HasDatetime()) {
- AppendAttribute(
- builder,
- "datetime",
- Format("%v", error.GetDatetime()),
- indent);
- }
-
- for (const auto& [key, value] : error.Attributes().ListPairs()) {
- TTokenizer tokenizer(value.AsStringBuf());
- YT_VERIFY(tokenizer.ParseNext());
- switch (tokenizer.GetCurrentType()) {
- case ETokenType::String:
- AppendAttribute(builder, key, TString(tokenizer.CurrentToken().GetStringValue()), indent);
- break;
- case ETokenType::Int64:
- AppendAttribute(builder, key, ToString(tokenizer.CurrentToken().GetInt64Value()), indent);
- break;
- case ETokenType::Uint64:
- AppendAttribute(builder, key, ToString(tokenizer.CurrentToken().GetUint64Value()), indent);
- break;
- case ETokenType::Double:
- AppendAttribute(builder, key, ToString(tokenizer.CurrentToken().GetDoubleValue()), indent);
- break;
- case ETokenType::Boolean:
- AppendAttribute(builder, key, TString(FormatBool(tokenizer.CurrentToken().GetBooleanValue())), indent);
- break;
- default:
- AppendAttribute(builder, key, ConvertToYsonString(value, EYsonFormat::Text).ToString(), indent);
- break;
- }
- }
-
- for (const auto& innerError : error.InnerErrors()) {
- builder->AppendChar('\n');
- AppendError(builder, innerError, indent + 2);
+ if (auto* attributes = error.MutableOriginAttributes()) {
+ return NOrigin::HasTracingAttributes(*attributes);
}
+ return false;
}
-} // namespace
-
-bool operator == (const TError& lhs, const TError& rhs)
+NTracing::TTraceId GetTraceId(const TError& error) noexcept
{
- if (!lhs.Impl_ && !rhs.Impl_) {
- return true;
+ if (auto* attributes = error.MutableOriginAttributes()) {
+ return NOrigin::GetTraceId(*attributes);
}
- return
- lhs.GetCode() == rhs.GetCode() &&
- lhs.GetMessage() == rhs.GetMessage() &&
- lhs.GetHost() == rhs.GetHost() &&
- lhs.GetDatetime() == rhs.GetDatetime() &&
- lhs.GetPid() == rhs.GetPid() &&
- lhs.GetTid() == rhs.GetTid() &&
- lhs.GetFid() == rhs.GetFid() &&
- lhs.GetTraceId() == rhs.GetTraceId() &&
- lhs.GetSpanId() == rhs.GetSpanId() &&
- lhs.Attributes() == rhs.Attributes() &&
- lhs.InnerErrors() == rhs.InnerErrors();
+ return NTracing::InvalidTraceId;
}
-void FormatValue(TStringBuilderBase* builder, const TError& error, TStringBuf /*spec*/)
-{
- AppendError(builder, error, 0);
-}
-
-void ToProto(NYT::NProto::TError* protoError, const TError& error)
+NTracing::TSpanId GetSpanId(const TError& error) noexcept
{
- if (!error.Impl_) {
- protoError->set_code(static_cast<int>(NYT::EErrorCode::OK));
- protoError->clear_message();
- return;
- }
-
- protoError->set_code(error.GetCode());
- protoError->set_message(error.GetMessage());
-
- protoError->clear_attributes();
- if (error.Impl_->HasAttributes()) {
- ToProto(protoError->mutable_attributes(), error.Attributes());
- }
-
- auto addAttribute = [&] (const TString& key, const auto& value) {
- auto* protoItem = protoError->mutable_attributes()->add_attributes();
- protoItem->set_key(key);
- protoItem->set_value(ConvertToYsonString(value).ToString());
- };
-
- if (error.HasOriginAttributes()) {
- static const TString HostKey("host");
- addAttribute(HostKey, error.GetHost());
-
- static const TString PidKey("pid");
- addAttribute(PidKey, error.GetPid());
-
- static const TString TidKey("tid");
- addAttribute(TidKey, error.GetTid());
-
- static const TString ThreadName("thread");
- addAttribute(ThreadName, error.GetThreadName());
-
- static const TString FidKey("fid");
- addAttribute(FidKey, error.GetFid());
- } else if (ErrorSanitizerEnabled() && error.HasHost()) {
- static const TString HostKey("host");
- addAttribute(HostKey, error.GetHost());
- }
-
- if (error.HasDatetime()) {
- static const TString DatetimeKey("datetime");
- addAttribute(DatetimeKey, error.GetDatetime());
- }
-
- if (error.HasTracingAttributes()) {
- static const TString TraceIdKey("trace_id");
- addAttribute(TraceIdKey, error.GetTraceId());
-
- static const TString SpanIdKey("span_id");
- addAttribute(SpanIdKey, error.GetSpanId());
- }
-
- protoError->clear_inner_errors();
- for (const auto& innerError : error.InnerErrors()) {
- ToProto(protoError->add_inner_errors(), innerError);
+ if (auto* attributes = error.MutableOriginAttributes()) {
+ return NOrigin::GetSpanId(*attributes);
}
+ return NTracing::InvalidSpanId;
}
-void FromProto(TError* error, const NYT::NProto::TError& protoError)
+void SetTracingAttributes(TError* error, const NTracing::TTracingAttributes& attributes)
{
- *error = {};
+ auto* originAttributes = error->MutableOriginAttributes();
- if (protoError.code() == static_cast<int>(NYT::EErrorCode::OK)) {
+ if (!originAttributes) {
return;
}
- error->SetCode(TErrorCode(protoError.code()));
- error->SetMessage(FromProto<TString>(protoError.message()));
- if (protoError.has_attributes()) {
- error->Impl_->SetAttributes(FromProto(protoError.attributes()));
- } else {
- error->Impl_->SetAttributes(nullptr);
- }
- *error->MutableInnerErrors() = FromProto<std::vector<TError>>(protoError.inner_errors());
+ NOrigin::UpdateTracingAttributes(originAttributes, attributes);
}
-void TraverseError(const TError& error, const TErrorVisitor& visitor, int depth)
-{
- visitor(error, depth);
- for (const auto& inner : error.InnerErrors()) {
- TraverseError(inner, visitor, depth + 1);
- }
-}
+////////////////////////////////////////////////////////////////////////////////
namespace {
// Errors whose depth exceeds |ErrorSerializationDepthLimit| are serialized
// as children of their ancestor on depth |ErrorSerializationDepthLimit - 1|.
+[[maybe_unused]]
void SerializeInnerErrors(TFluentMap fluent, const TError& error, int depth)
{
if (depth >= ErrorSerializationDepthLimit) {
@@ -1215,13 +316,14 @@ void SerializeInnerErrors(TFluentMap fluent, const TError& error, int depth)
} // namespace
+////////////////////////////////////////////////////////////////////////////////
+
void Serialize(
const TError& error,
IYsonConsumer* consumer,
const std::function<void(IYsonConsumer*)>* valueProducer,
int depth)
{
- auto& errorSanitizerEnabled = ErrorSanitizerEnabled();
BuildYsonFluently(consumer)
.BeginMap()
.Item("code").Value(error.GetCode())
@@ -1229,23 +331,23 @@ void Serialize(
.Item("attributes").DoMap([&] (auto fluent) {
if (error.HasOriginAttributes()) {
fluent
- .Item("host").Value(error.GetHost())
+ .Item("host").Value(GetHost(error))
.Item("pid").Value(error.GetPid())
.Item("tid").Value(error.GetTid())
.Item("thread").Value(error.GetThreadName())
- .Item("fid").Value(error.GetFid());
- } else if (errorSanitizerEnabled && error.HasHost()) {
+ .Item("fid").Value(GetFid(error));
+ } else if (IsErrorSanitizerEnabled() && HasHost(error)) {
fluent
- .Item("host").Value(error.GetHost());
+ .Item("host").Value(GetHost(error));
}
if (error.HasDatetime()) {
fluent
.Item("datetime").Value(error.GetDatetime());
}
- if (error.HasTracingAttributes()) {
+ if (HasTracingAttributes(error)) {
fluent
- .Item("trace_id").Value(error.GetTraceId())
- .Item("span_id").Value(error.GetSpanId());
+ .Item("trace_id").Value(GetTraceId(error))
+ .Item("span_id").Value(GetSpanId(error));
}
if (depth > ErrorSerializationDepthLimit && !error.Attributes().Contains(OriginalErrorDepthAttribute)) {
fluent
@@ -1269,7 +371,6 @@ void Serialize(
(*valueProducer)(consumer);
})
.EndMap();
-
}
void Deserialize(TError& error, const NYTree::INodePtr& node)
@@ -1284,23 +385,22 @@ void Deserialize(TError& error, const NYTree::INodePtr& node)
return;
}
- auto result = std::make_unique<TError::TImpl>();
- result->SetCode(code);
+ error.SetCode(code);
static const TString MessageKey("message");
- result->SetMessage(mapNode->GetChildValueOrThrow<TString>(MessageKey));
+ error.SetMessage(mapNode->GetChildValueOrThrow<TString>(MessageKey));
static const TString AttributesKey("attributes");
- result->SetAttributes(IAttributeDictionary::FromMap(mapNode->GetChildOrThrow(AttributesKey)->AsMap()));
+ auto attributes = IAttributeDictionary::FromMap(mapNode->GetChildOrThrow(AttributesKey)->AsMap());
+
+ error.SetAttributes(std::move(attributes));
static const TString InnerErrorsKey("inner_errors");
if (auto innerErrorsNode = mapNode->FindChild(InnerErrorsKey)) {
for (const auto& innerErrorNode : innerErrorsNode->AsList()->GetChildren()) {
- result->MutableInnerErrors()->push_back(ConvertTo<TError>(innerErrorNode));
+ error.MutableInnerErrors()->push_back(ConvertTo<TError>(innerErrorNode));
}
}
-
- error = TError(std::move(result));
}
void Deserialize(TError& error, NYson::TYsonPullParserCursor* cursor)
@@ -1310,64 +410,211 @@ void Deserialize(TError& error, NYson::TYsonPullParserCursor* cursor)
////////////////////////////////////////////////////////////////////////////////
-TError& TError::operator <<= (const TErrorAttribute& attribute) &
+void ToProto(NYT::NProto::TError* protoError, const TError& error)
{
- MutableAttributes()->SetYson(attribute.Key, attribute.Value);
- return *this;
-}
+ if (error.IsOK()) {
+ protoError->set_code(static_cast<int>(NYT::EErrorCode::OK));
+ protoError->clear_message();
+ return;
+ }
-TError& TError::operator <<= (const std::vector<TErrorAttribute>& attributes) &
-{
- for (const auto& attribute : attributes) {
- MutableAttributes()->SetYson(attribute.Key, attribute.Value);
+ protoError->set_code(error.GetCode());
+ protoError->set_message(error.GetMessage());
+
+ protoError->clear_attributes();
+ if (error.HasAttributes()) {
+ ToProto(protoError->mutable_attributes(), error.Attributes());
+ }
+
+ auto addAttribute = [&] (const TString& key, const auto& value) {
+ auto* protoItem = protoError->mutable_attributes()->add_attributes();
+ protoItem->set_key(key);
+ protoItem->set_value(ConvertToYsonString(value).ToString());
+ };
+
+ if (error.HasOriginAttributes()) {
+ static const TString HostKey("host");
+ addAttribute(HostKey, GetHost(error));
+
+ static const TString PidKey("pid");
+ addAttribute(PidKey, error.GetPid());
+
+ static const TString TidKey("tid");
+ addAttribute(TidKey, error.GetTid());
+
+ static const TString ThreadName("thread");
+ addAttribute(ThreadName, error.GetThreadName());
+
+ static const TString FidKey("fid");
+ addAttribute(FidKey, GetFid(error));
+ } else if (IsErrorSanitizerEnabled() && HasHost(error)) {
+ static const TString HostKey("host");
+ addAttribute(HostKey, GetHost(error));
+ }
+
+ if (error.HasDatetime()) {
+ static const TString DatetimeKey("datetime");
+ addAttribute(DatetimeKey, error.GetDatetime());
+ }
+
+ if (HasTracingAttributes(error)) {
+ static const TString TraceIdKey("trace_id");
+ addAttribute(TraceIdKey, GetTraceId(error));
+
+ static const TString SpanIdKey("span_id");
+ addAttribute(SpanIdKey, GetSpanId(error));
+ }
+
+ protoError->clear_inner_errors();
+ for (const auto& innerError : error.InnerErrors()) {
+ ToProto(protoError->add_inner_errors(), innerError);
}
- return *this;
}
-TError& TError::operator <<= (const TError& innerError) &
+void FromProto(TError* error, const NYT::NProto::TError& protoError)
{
- MutableInnerErrors()->push_back(innerError);
- return *this;
+ *error = {};
+
+ if (protoError.code() == static_cast<int>(NYT::EErrorCode::OK)) {
+ return;
+ }
+
+ error->SetCode(TErrorCode(protoError.code()));
+ error->SetMessage(FromProto<TString>(protoError.message()));
+ if (protoError.has_attributes()) {
+ auto attributes = FromProto(protoError.attributes());
+
+ error->SetAttributes(std::move(attributes));
+ }
+ *error->MutableInnerErrors() = FromProto<std::vector<TError>>(protoError.inner_errors());
}
-TError& TError::operator <<= (TError&& innerError) &
+////////////////////////////////////////////////////////////////////////////////
+
+void TErrorSerializer::Save(TStreamSaveContext& context, const TErrorCode& errorCode)
{
- MutableInnerErrors()->push_back(std::move(innerError));
- return *this;
+ NYT::Save(context, static_cast<int>(errorCode));
}
-TError& TError::operator <<= (const std::vector<TError>& innerErrors) &
+void TErrorSerializer::Load(TStreamLoadContext& context, TErrorCode& errorCode)
{
- MutableInnerErrors()->insert(
- MutableInnerErrors()->end(),
- innerErrors.begin(),
- innerErrors.end());
- return *this;
+ int value = 0;
+ NYT::Load(context, value);
+ errorCode = TErrorCode{value};
}
-TError& TError::operator <<= (std::vector<TError>&& innerErrors) &
+////////////////////////////////////////////////////////////////////////////////
+
+void TErrorSerializer::Save(TStreamSaveContext& context, const TError& error)
{
- MutableInnerErrors()->insert(
- MutableInnerErrors()->end(),
- std::make_move_iterator(innerErrors.begin()),
- std::make_move_iterator(innerErrors.end()));
- return *this;
+ using NYT::Save;
+
+ if (error.IsOK()) {
+ // Fast path.
+ Save(context, TErrorCode(NYT::EErrorCode::OK)); // code
+ Save(context, TStringBuf()); // message
+ Save(context, IAttributeDictionaryPtr()); // attributes
+ Save(context, std::vector<TError>()); // inner errors
+ return;
+ }
+
+ Save(context, error.GetCode());
+ Save(context, error.GetMessage());
+
+ // Cf. TAttributeDictionaryValueSerializer.
+ auto attributePairs = error.Attributes().ListPairs();
+ size_t attributeCount = attributePairs.size();
+ if (error.HasOriginAttributes()) {
+ attributeCount += 5;
+ }
+ if (error.HasDatetime()) {
+ attributeCount += 1;
+ }
+ if (HasTracingAttributes(error)) {
+ attributeCount += 2;
+ }
+
+ if (attributeCount > 0) {
+ // Cf. TAttributeDictionaryRefSerializer.
+ Save(context, true);
+
+ TSizeSerializer::Save(context, attributeCount);
+
+ auto saveAttribute = [&] (const TString& key, const auto& value) {
+ Save(context, key);
+ Save(context, ConvertToYsonString(value));
+ };
+
+ if (error.HasOriginAttributes()) {
+ static const TString HostKey("host");
+ saveAttribute(HostKey, GetHost(error));
+
+ static const TString PidKey("pid");
+ saveAttribute(PidKey, error.GetPid());
+
+ static const TString TidKey("tid");
+ saveAttribute(TidKey, error.GetTid());
+
+ static const TString ThreadNameKey("thread");
+ saveAttribute(ThreadNameKey, error.GetThreadName());
+
+ static const TString FidKey("fid");
+ saveAttribute(FidKey, GetFid(error));
+ }
+
+ if (error.HasDatetime()) {
+ static const TString DatetimeKey("datetime");
+ saveAttribute(DatetimeKey, error.GetDatetime());
+ }
+
+ if (HasTracingAttributes(error)) {
+ static const TString TraceIdKey("trace_id");
+ saveAttribute(TraceIdKey, GetTraceId(error));
+
+ static const TString SpanIdKey("span_id");
+ saveAttribute(SpanIdKey, GetSpanId(error));
+ }
+
+ std::sort(attributePairs.begin(), attributePairs.end(), [] (const auto& lhs, const auto& rhs) {
+ return lhs.first < rhs.first;
+ });
+ for (const auto& [key, value] : attributePairs) {
+ Save(context, key);
+ Save(context, value);
+ }
+ } else {
+ Save(context, false);
+ }
+
+ Save(context, error.InnerErrors());
}
-TError& TError::operator <<= (const NYTree::IAttributeDictionary& attributes) &
+void TErrorSerializer::Load(TStreamLoadContext& context, TError& error)
{
- MutableAttributes()->MergeFrom(attributes);
- return *this;
-}
+ using NYT::Load;
-////////////////////////////////////////////////////////////////////////////////
+ error = {};
-const char* TErrorException::what() const noexcept
-{
- if (CachedWhat_.empty()) {
- CachedWhat_ = ToString(Error_);
+ auto code = Load<TErrorCode>(context);
+ auto message = Load<TString>(context);
+
+ IAttributeDictionaryPtr attributes;
+ if (Load<bool>(context)) {
+ attributes = CreateEphemeralAttributes();
+ TAttributeDictionarySerializer::LoadNonNull(context, attributes);
}
- return CachedWhat_.data();
+
+ auto innerErrors = Load<std::vector<TError>>(context);
+
+ if (code == NYT::EErrorCode::OK) {
+ // Fast path.
+ return;
+ }
+
+ error.SetCode(code);
+ error.SetMessage(std::move(message));
+ error.SetAttributes(std::move(attributes));
+ *error.MutableInnerErrors() = std::move(innerErrors);
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/yt/yt/core/misc/error.h b/yt/yt/core/misc/error.h
index 52ba6cfc93..18c984803d 100644
--- a/yt/yt/core/misc/error.h
+++ b/yt/yt/core/misc/error.h
@@ -1,6 +1,7 @@
#pragma once
#include "public.h"
+#include "stripped_error.h"
#include <yt/yt/core/yson/public.h>
@@ -10,299 +11,37 @@
#include <yt/yt/core/concurrency/public.h>
-#include <yt/yt/core/threading/public.h>
-
-#include <library/cpp/yt/yson/public.h>
-
-#include <library/cpp/yt/yson_string/convert.h>
-#include <library/cpp/yt/yson_string/string.h>
-
-#include <library/cpp/yt/misc/property.h>
-
-#include <util/system/compiler.h>
-#include <util/system/getpid.h>
-
-#include <util/generic/size_literals.h>
-
-#include <type_traits>
+#include <library/cpp/yt/error/origin_attributes.h>
namespace NYT {
////////////////////////////////////////////////////////////////////////////////
-//! An opaque wrapper around |int| value capable of conversions from |int|s and
-//! arbitrary enum types.
-class TErrorCode
-{
-public:
- using TUnderlying = int;
-
- constexpr TErrorCode();
- explicit constexpr TErrorCode(int value);
- template <class E>
- requires std::is_enum_v<E>
- constexpr TErrorCode(E value);
+namespace NOrigin {
- constexpr operator int() const;
+void EnableOriginOverrides();
- template <class E>
- requires std::is_enum_v<E>
- constexpr operator E() const;
-
- void Save(TStreamSaveContext& context) const;
- void Load(TStreamLoadContext& context);
-
- template <class E>
- requires std::is_enum_v<E>
- constexpr bool operator == (E rhs) const;
-
- constexpr bool operator == (TErrorCode rhs) const;
-
-private:
- int Value_;
-};
-
-void FormatValue(TStringBuilderBase* builder, TErrorCode code, TStringBuf spec);
+} // namespace NOrigin
////////////////////////////////////////////////////////////////////////////////
-constexpr int ErrorSerializationDepthLimit = 16;
+bool HasHost(const TError& error) noexcept;
+TStringBuf GetHost(const TError& error) noexcept;
-////////////////////////////////////////////////////////////////////////////////
+NConcurrency::TFiberId GetFid(const TError& error) noexcept;
-//! 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();
+bool HasTracingAttributes(const TError& error) noexcept;
+NTracing::TTraceId GetTraceId(const TError& error) noexcept;
+NTracing::TSpanId GetSpanId(const TError& error) noexcept;
-private:
- const bool SavedEnabled_;
- const TInstant SavedDatetimeOverride_;
- const TSharedRef SavedLocalHostNameOverride_;
-};
+void SetTracingAttributes(TError* error, const NTracing::TTracingAttributes& attributes);
////////////////////////////////////////////////////////////////////////////////
-struct TErrorAttribute
-{
- template <class T>
- TErrorAttribute(const TString& key, const T& value)
- : Key(key)
- , Value(NYson::ConvertToYsonString(value))
- { }
-
- TErrorAttribute(const TString& key, const NYson::TYsonString& value)
- : Key(key)
- , Value(value)
- { }
-
- TString Key;
- NYson::TYsonString Value;
-};
+constexpr int ErrorSerializationDepthLimit = 16;
////////////////////////////////////////////////////////////////////////////////
-template <class TValue>
-concept CErrorNestable = requires (TError& error, TValue&& operand)
-{
- { error <<= std::forward<TValue>(operand) } -> std::same_as<TError&>;
-};
-
-template <>
-class [[nodiscard]] TErrorOr<void>
-{
-public:
- TErrorOr();
- ~TErrorOr();
-
- TErrorOr(const TError& other);
- TErrorOr(TError&& other) noexcept;
-
- TErrorOr(const std::exception& ex);
-
- struct TDisableFormat
- { };
- static constexpr TDisableFormat DisableFormat = {};
-
- TErrorOr(TString message, TDisableFormat);
- TErrorOr(TErrorCode code, TString message, TDisableFormat);
-
- template <class... TArgs>
- explicit TErrorOr(
- TFormatString<TArgs...> format,
- TArgs&&... arg);
-
- template <class... TArgs>
- TErrorOr(
- TErrorCode code,
- TFormatString<TArgs...> format,
- TArgs&&... args);
-
- TError& operator = (const TError& other);
- TError& operator = (TError&& other) noexcept;
-
- static TError FromSystem();
- static TError FromSystem(int error);
- static TError FromSystem(const TSystemError& error);
-
- TErrorCode GetCode() const;
- TError& SetCode(TErrorCode code);
-
- TErrorCode GetNonTrivialCode() const;
- THashSet<TErrorCode> GetDistinctNonTrivialErrorCodes() const;
-
- const TString& GetMessage() const;
- TError& SetMessage(TString message);
-
- bool HasHost() const;
- TStringBuf GetHost() const;
-
- bool HasOriginAttributes() const;
- TProcessId GetPid() const;
- TStringBuf GetThreadName() const;
- NThreading::TThreadId GetTid() const;
- NConcurrency::TFiberId GetFid() const;
-
- bool HasDatetime() const;
- TInstant GetDatetime() const;
-
- bool HasTracingAttributes() const;
- void SetTracingAttributes(NTracing::TTracingAttributes tracingAttributes);
- NTracing::TTraceId GetTraceId() const;
- NTracing::TSpanId GetSpanId() const;
-
- const NYTree::IAttributeDictionary& Attributes() const;
- NYTree::IAttributeDictionary* MutableAttributes();
-
- const std::vector<TError>& InnerErrors() const;
- std::vector<TError>* MutableInnerErrors();
-
- TError Truncate(
- int maxInnerErrorCount = 2,
- i64 stringLimit = 16_KB,
- const THashSet<TStringBuf>& attributeWhitelist = {}) const &;
- TError Truncate(
- int maxInnerErrorCount = 2,
- i64 stringLimit = 16_KB,
- const THashSet<TStringBuf>& attributeWhitelist = {}) &&;
-
- bool IsOK() const;
-
- template <class U>
- requires (!CStringLiteral<std::remove_cvref_t<U>>)
- void ThrowOnError(U&& u) const &;
- template <class... TArgs>
- void ThrowOnError(TFormatString<TArgs...> format, TArgs&&... args) const &;
- template <class... TArgs>
- void ThrowOnError(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) const &;
- inline void ThrowOnError() const &;
-
- template <class U>
- requires (!CStringLiteral<std::remove_cvref_t<U>>)
- void ThrowOnError(U&& u) &&;
- template <class... TArgs>
- void ThrowOnError(TFormatString<TArgs...> format, TArgs&&... args) &&;
- template <class... TArgs>
- void ThrowOnError(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) &&;
- inline void ThrowOnError() &&;
-
- template <CInvocable<bool(const TError&)> TFilter>
- std::optional<TError> FindMatching(const TFilter& filter) const;
- template <CInvocable<bool(TErrorCode)> TFilter>
- std::optional<TError> FindMatching(const TFilter& filter) const;
- std::optional<TError> FindMatching(TErrorCode code) const;
- std::optional<TError> FindMatching(const THashSet<TErrorCode>& codes) const;
-
- template <class U>
- requires (!CStringLiteral<std::remove_cvref_t<U>>)
- TError Wrap(U&& u) const &;
- template <class... TArgs>
- TError Wrap(TFormatString<TArgs...> format, TArgs&&... args) const &;
- template <class... TArgs>
- TError Wrap(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) const &;
- TError Wrap() const &;
-
- template <class U>
- requires (!CStringLiteral<std::remove_cvref_t<U>>)
- TError Wrap(U&& u) &&;
- template <class... TArgs>
- TError Wrap(TFormatString<TArgs...> format, TArgs&&... args) &&;
- template <class... TArgs>
- TError Wrap(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) &&;
- TError Wrap() &&;
-
- //! Perform recursive aggregation of error codes and messages over the error tree.
- //! Result of this aggregation is suitable for error clustering in groups of
- //! "similar" errors. Refer to yt/yt/library/error_skeleton/skeleton_ut.cpp for examples.
- //!
- //! This method builds skeleton from scratch by doing complete error tree traversal,
- //! so calling it in computationally hot code is discouraged.
- //!
- //! In order to prevent core -> re2 dependency, implementation belongs to a separate library
- //! yt/yt/library/error_skeleton. Calling this method without PEERDIR'ing implementation
- //! results in an exception.
- TString GetSkeleton() const;
-
- void Save(TStreamSaveContext& context) const;
- void Load(TStreamLoadContext& context);
-
- TError& operator <<= (const TErrorAttribute& attribute) &;
- TError& operator <<= (const std::vector<TErrorAttribute>& attributes) &;
- TError& operator <<= (const TError& innerError) &;
- TError& operator <<= (TError&& innerError) &;
- TError& operator <<= (const std::vector<TError>& innerErrors) &;
- TError& operator <<= (std::vector<TError>&& innerErrors) &;
- TError& operator <<= (const NYTree::IAttributeDictionary& attributes) &;
-
- template <CErrorNestable TValue>
- TError&& operator << (TValue&& operand) &&;
- template <CErrorNestable TValue>
- TError operator << (TValue&& operand) const &;
-
- template <CErrorNestable TValue>
- TError&& operator << (const std::optional<TValue>& operand) &&;
- template <CErrorNestable TValue>
- TError operator << (const std::optional<TValue>& operand) const &;
-
-private:
- class TImpl;
- std::unique_ptr<TImpl> Impl_;
-
- explicit TErrorOr(std::unique_ptr<TImpl> impl);
-
- void MakeMutable();
-
- friend bool operator == (const TError& lhs, const TError& rhs);
-
- friend void ToProto(NProto::TError* protoError, const TError& error);
- friend void FromProto(TError* error, const NProto::TError& protoError);
-
- friend void Serialize(
- const TError& error,
- NYson::IYsonConsumer* consumer,
- const std::function<void(NYson::IYsonConsumer*)>* valueProducer,
- int depth);
- friend void Deserialize(TError& error, const NYTree::INodePtr& node);
- friend void Deserialize(TError& error, NYson::TYsonPullParserCursor* cursor);
-};
-
-bool operator == (const TError& lhs, const TError& rhs);
-
-void ToProto(NProto::TError* protoError, const TError& error);
-void FromProto(TError* error, const NProto::TError& protoError);
-
-using TErrorVisitor = std::function<void(const TError&, int depth)>;
-
-//! Traverses the error tree in DFS order.
-void TraverseError(
- const TError& error,
- const TErrorVisitor& visitor,
- int depth = 0);
-
void Serialize(
const TError& error,
NYson::IYsonConsumer* consumer,
@@ -311,196 +50,44 @@ void Serialize(
void Deserialize(
TError& error,
const NYTree::INodePtr& node);
-
-void FormatValue(TStringBuilderBase* builder, const TError& error, TStringBuf spec);
+void Deserialize(
+ TError& error,
+ NYson::TYsonPullParserCursor* cursor);
////////////////////////////////////////////////////////////////////////////////
-template <class T>
-struct TErrorTraits
-{
- using TWrapped = TErrorOr<T>;
- using TUnwrapped = T;
-};
-
-template <class T>
-struct TErrorTraits<TErrorOr<T>>
-{
- using TUnderlying = T;
- using TWrapped = TErrorOr<T>;
- using TUnwrapped = T;
-};
+void ToProto(NProto::TError* protoError, const TError& error);
+void FromProto(TError* error, const NProto::TError& protoError);
////////////////////////////////////////////////////////////////////////////////
-class TErrorException
- : public std::exception
+struct TErrorSerializer
{
-public:
- DEFINE_BYREF_RW_PROPERTY(TError, Error);
-
-public:
- TErrorException() = default;
- TErrorException(const TErrorException& other) = default;
- TErrorException(TErrorException&& other) = default;
-
- const char* what() const noexcept override;
+ static void Save(TStreamSaveContext& context, const TErrorCode& errorCode);
+ static void Load(TStreamLoadContext& context, TErrorCode& errorCode);
-private:
- mutable TString CachedWhat_;
+ static void Save(TStreamSaveContext& context, const TError& error);
+ static void Load(TStreamLoadContext& context, TError& error);
};
-// Make these templates to avoid type erasure during throw.
-template <class TException>
- requires std::derived_from<std::remove_cvref_t<TException>, TErrorException>
-TException&& operator <<= (TException&& ex, const TError& error);
-template <class TException>
- requires std::derived_from<std::remove_cvref_t<TException>, TErrorException>
-TException&& operator <<= (TException&& ex, TError&& error);
-
-////////////////////////////////////////////////////////////////////////////////
-
-namespace NDetail {
-
-struct TErrorAdaptor
+template <class C, class Cond>
+struct TSerializerTraits<
+ TErrorCode,
+ C,
+ Cond>
{
- template <class TArg>
- requires std::constructible_from<TError, TArg>
- TError operator << (TArg&& rhs) const;
-
- template <class TArg>
- requires
- std::constructible_from<TError, TArg> &&
- std::derived_from<std::remove_cvref_t<TArg>, TError>
- TArg&& operator << (TArg&& rhs) const;
+ using TSerializer = TErrorSerializer;
};
-// Make these to correctly forward TError to Wrap call.
-template <class TErrorLike, class U>
- requires
- std::derived_from<std::remove_cvref_t<TErrorLike>, TError> &&
- (!CStringLiteral<std::remove_cvref_t<U>>)
-void ThrowErrorExceptionIfFailed(TErrorLike&& error, U&& u);
-
-template <class TErrorLike, class... TArgs>
- requires std::derived_from<std::remove_cvref_t<TErrorLike>, TError>
-void ThrowErrorExceptionIfFailed(TErrorLike&& error, TFormatString<TArgs...> format, TArgs&&... args);
-
-template <class TErrorLike, class... TArgs>
- requires std::derived_from<std::remove_cvref_t<TErrorLike>, TError>
-void ThrowErrorExceptionIfFailed(TErrorLike&& error, TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args);
-
-template <class TErrorLike>
- requires std::derived_from<std::remove_cvref_t<TErrorLike>, TError>
-void ThrowErrorExceptionIfFailed(TErrorLike&& error);
-
-} // namespace NDetail
-
-////////////////////////////////////////////////////////////////////////////////
-
-#define THROW_ERROR \
- throw ::NYT::TErrorException() <<= ::NYT::NDetail::TErrorAdaptor() <<
-
-#define THROW_ERROR_EXCEPTION(head, ...) \
- THROW_ERROR ::NYT::TError(head __VA_OPT__(,) __VA_ARGS__)
-
-#define THROW_ERROR_EXCEPTION_IF_FAILED(error, ...) \
- ::NYT::NDetail::ThrowErrorExceptionIfFailed((error) __VA_OPT__(,) __VA_ARGS__); \
-
-#define THROW_ERROR_EXCEPTION_UNLESS(condition, head, ...) \
- if ((condition)) {\
- } else { \
- THROW_ERROR ::NYT::TError(head __VA_OPT__(,) __VA_ARGS__); \
- }
-
-#define THROW_ERROR_EXCEPTION_IF(condition, head, ...) \
- THROW_ERROR_EXCEPTION_UNLESS(!(condition), head, __VA_ARGS__)
-
-////////////////////////////////////////////////////////////////////////////////
-
-template <class T>
-class [[nodiscard]] TErrorOr
- : public TError
+template <class C, class Cond>
+struct TSerializerTraits<
+ TError,
+ C,
+ Cond>
{
-public:
- TErrorOr();
-
- TErrorOr(const T& value);
- TErrorOr(T&& value) noexcept;
-
- TErrorOr(const TErrorOr<T>& other);
- TErrorOr(TErrorOr<T>&& other) noexcept;
-
- TErrorOr(const TError& other);
- TErrorOr(TError&& other) noexcept;
-
- TErrorOr(const std::exception& ex);
-
- template <class U>
- TErrorOr(const TErrorOr<U>& other);
- template <class U>
- TErrorOr(TErrorOr<U>&& other) noexcept;
-
- TErrorOr<T>& operator = (const TErrorOr<T>& other)
- requires std::is_copy_assignable_v<T>;
- TErrorOr<T>& operator = (TErrorOr<T>&& other) noexcept
- requires std::is_nothrow_move_assignable_v<T>;
-
- const T& Value() const & Y_LIFETIME_BOUND;
- T& Value() & Y_LIFETIME_BOUND;
- T&& Value() && Y_LIFETIME_BOUND;
-
- template <class U>
- requires (!CStringLiteral<std::remove_cvref_t<U>>)
- const T& ValueOrThrow(U&& u) const & Y_LIFETIME_BOUND;
- template <class... TArgs>
- const T& ValueOrThrow(TFormatString<TArgs...> format, TArgs&&... args) const & Y_LIFETIME_BOUND;
- template <class... TArgs>
- const T& ValueOrThrow(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) const & Y_LIFETIME_BOUND;
- const T& ValueOrThrow() const & Y_LIFETIME_BOUND;
-
- template <class U>
- requires (!CStringLiteral<std::remove_cvref_t<U>>)
- T& ValueOrThrow(U&& u) & Y_LIFETIME_BOUND;
- template <class... TArgs>
- T& ValueOrThrow(TFormatString<TArgs...> format, TArgs&&... args) & Y_LIFETIME_BOUND;
- template <class... TArgs>
- T& ValueOrThrow(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) & Y_LIFETIME_BOUND;
- T& ValueOrThrow() & Y_LIFETIME_BOUND;
-
- template <class U>
- requires (!CStringLiteral<std::remove_cvref_t<U>>)
- T&& ValueOrThrow(U&& u) && Y_LIFETIME_BOUND;
- template <class... TArgs>
- T&& ValueOrThrow(TFormatString<TArgs...> format, TArgs&&... args) && Y_LIFETIME_BOUND;
- template <class... TArgs>
- T&& ValueOrThrow(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) && Y_LIFETIME_BOUND;
- T&& ValueOrThrow() && Y_LIFETIME_BOUND;
-
- const T& ValueOrDefault(const T& defaultValue Y_LIFETIME_BOUND) const & Y_LIFETIME_BOUND;
- T& ValueOrDefault(T& defaultValue Y_LIFETIME_BOUND) & Y_LIFETIME_BOUND;
- constexpr T ValueOrDefault(T&& defaultValue) const &;
- constexpr T ValueOrDefault(T&& defaultValue) &&;
-
-private:
- std::optional<T> Value_;
+ using TSerializer = TErrorSerializer;
};
-template <class T>
-void FormatValue(TStringBuilderBase* builder, const TErrorOr<T>& error, TStringBuf spec);
-
-////////////////////////////////////////////////////////////////////////////////
-
-template <class F, class... As>
-auto RunNoExcept(F&& functor, As&&... args) noexcept -> decltype(functor(std::forward<As>(args)...))
-{
- return functor(std::forward<As>(args)...);
-}
-
////////////////////////////////////////////////////////////////////////////////
} // namespace NYT
-
-#define ERROR_INL_H_
-#include "error-inl.h"
-#undef ERROR_INL_H_
diff --git a/yt/yt/core/misc/origin_attributes.cpp b/yt/yt/core/misc/origin_attributes.cpp
new file mode 100644
index 0000000000..332d1f58b2
--- /dev/null
+++ b/yt/yt/core/misc/origin_attributes.cpp
@@ -0,0 +1,50 @@
+#include "origin_attributes.h"
+
+#include <yt/yt/core/ytree/attributes.h>
+
+namespace NYT::NDetail {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TOriginAttributes ExtractFromDictionary(const NYTree::IAttributeDictionaryPtr& attributes)
+{
+ using TFunctor = TOriginAttributes(*)(const NYTree::IAttributeDictionaryPtr&);
+
+ if (auto strong = NGlobal::GetErasedVariable(ExtractFromDictionaryTag)) {
+ return strong->AsConcrete<TFunctor>()(attributes);
+ }
+
+ return ExtractFromDictionaryDefault(attributes);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TOriginAttributes ExtractFromDictionaryDefault(const NYTree::IAttributeDictionaryPtr& attributes)
+{
+ TOriginAttributes result;
+ if (!attributes) {
+ return result;
+ }
+
+ static const TString HostKey("host");
+ result.HostHolder = TSharedRef::FromString(attributes->GetAndRemove<TString>(HostKey, TString()));
+ result.Host = result.HostHolder.empty() ? TStringBuf() : TStringBuf(result.HostHolder.Begin(), result.HostHolder.End());
+
+ static const TString DatetimeKey("datetime");
+ result.Datetime = attributes->GetAndRemove<TInstant>(DatetimeKey, TInstant());
+
+ static const TString PidKey("pid");
+ result.Pid = attributes->GetAndRemove<TProcessId>(PidKey, 0);
+
+ static const TString TidKey("tid");
+ result.Tid = attributes->GetAndRemove<NThreading::TThreadId>(TidKey, NThreading::InvalidThreadId);
+
+ static const TString ThreadNameKey("thread");
+ result.ThreadName = attributes->GetAndRemove<TString>(ThreadNameKey, TString());
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDetail
diff --git a/yt/yt/core/misc/origin_attributes.h b/yt/yt/core/misc/origin_attributes.h
new file mode 100644
index 0000000000..5848431fc8
--- /dev/null
+++ b/yt/yt/core/misc/origin_attributes.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <yt/yt/core/ytree/public.h>
+
+#include <library/cpp/yt/error/origin_attributes.h>
+
+// TODO(arkady-e1ppa): Move this to library/cpp/yt/error when deps on ytree are gone.
+
+namespace NYT::NDetail {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Weak symbol.
+TOriginAttributes ExtractFromDictionary(const NYTree::IAttributeDictionaryPtr& attributes);
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Default impl of weak symbol.
+TOriginAttributes ExtractFromDictionaryDefault(const NYTree::IAttributeDictionaryPtr& attributes);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NDetail
diff --git a/yt/yt/core/misc/proc.h b/yt/yt/core/misc/proc.h
index 7eafd72113..2ae656326a 100644
--- a/yt/yt/core/misc/proc.h
+++ b/yt/yt/core/misc/proc.h
@@ -2,7 +2,7 @@
#include "common.h"
-#include <yt/yt/core/misc/error.h>
+#include <yt/yt/core/misc/stripped_error.h>
#include <util/system/file.h>
diff --git a/yt/yt/core/misc/error-inl.h b/yt/yt/core/misc/stripped_error-inl.h
index 449303229d..5c2effc9aa 100644
--- a/yt/yt/core/misc/error-inl.h
+++ b/yt/yt/core/misc/stripped_error-inl.h
@@ -1,7 +1,7 @@
-#ifndef ERROR_INL_H_
-#error "Direct inclusion of this file is not allowed, include error.h"
+#ifndef STRIPPED_ERROR_INL_H_
+#error "Direct inclusion of this file is not allowed, include stripped_error.h"
// For the sake of sane code completion.
-#include "error.h"
+#include "stripped_error.h"
#endif
#include <library/cpp/yt/string/format.h>
@@ -484,14 +484,6 @@ constexpr T TErrorOr<T>::ValueOrDefault(T&& defaultValue) &&
////////////////////////////////////////////////////////////////////////////////
-template <class T>
-void FormatValue(TStringBuilderBase* builder, const TErrorOr<T>& error, TStringBuf spec)
-{
- FormatValue(builder, static_cast<const TError&>(error), spec);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
template <class TException>
requires std::derived_from<std::remove_cvref_t<TException>, TErrorException>
TException&& operator <<= (TException&& ex, const TError& error)
@@ -564,4 +556,12 @@ void ThrowErrorExceptionIfFailed(TErrorLike&& error)
////////////////////////////////////////////////////////////////////////////////
+template <class T>
+void FormatValue(TStringBuilderBase* builder, const TErrorOr<T>& error, TStringBuf spec)
+{
+ FormatValue(builder, static_cast<const TError&>(error), spec);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
} // namespace NYT
diff --git a/yt/yt/core/misc/stripped_error.cpp b/yt/yt/core/misc/stripped_error.cpp
new file mode 100644
index 0000000000..732ac163ed
--- /dev/null
+++ b/yt/yt/core/misc/stripped_error.cpp
@@ -0,0 +1,858 @@
+#include "stripped_error.h"
+
+// TODO(arkady-e1ppa): core/misc deps can easily be moved
+// to relevant library/cpp/yt sections.
+#include <yt/yt/core/misc/string_helpers.h>
+#include <yt/yt/core/misc/proc.h>
+
+// TODO(arkady-e1ppa): core/ytree deps removal requires making
+// a separate class that has some features of
+// IAttributeDictionary. Namely, it needs to
+// be created, have TYsonString added to it
+// merged with other dictionary and deep copied.
+// NB: We don't need printability since TError from
+// this file is not printable.
+#include <yt/yt/core/ytree/attributes.h>
+
+#include <yt/yt/core/misc/origin_attributes.h>
+
+#include <library/cpp/yt/exception/exception.h>
+
+#include <library/cpp/yt/error/origin_attributes.h>
+
+#include <library/cpp/yt/yson_string/string.h>
+
+#include <util/string/subst.h>
+
+#include <util/system/error.h>
+#include <util/system/thread.h>
+
+namespace NYT {
+
+using namespace NYTree;
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, TErrorCode code, TStringBuf spec)
+{
+ FormatValue(builder, static_cast<int>(code), spec);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+constexpr TStringBuf ErrorMessageTruncatedSuffix = "...<message truncated>";
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TError::TImpl
+{
+public:
+ TImpl()
+ : Code_(NYT::EErrorCode::OK)
+ { }
+
+ TImpl(const TError::TImpl& other)
+ : Code_(other.Code_)
+ , Message_(other.Message_)
+ , OriginAttributes_(other.OriginAttributes_)
+ , Attributes_(other.Attributes_ ? other.Attributes_->Clone() : nullptr)
+ , InnerErrors_(other.InnerErrors_)
+ { }
+
+ explicit TImpl(TString message)
+ : Code_(NYT::EErrorCode::Generic)
+ , Message_(std::move(message))
+ {
+ OriginAttributes_.Capture();
+ }
+
+ TImpl(TErrorCode code, TString message)
+ : Code_(code)
+ , Message_(std::move(message))
+ {
+ if (!IsOK()) {
+ OriginAttributes_.Capture();
+ }
+ }
+
+ bool IsOK() const noexcept
+ {
+ return Code_ == NYT::EErrorCode::OK;
+ }
+
+ TErrorCode GetCode() const noexcept
+ {
+ return Code_;
+ }
+
+ void SetCode(TErrorCode code) noexcept
+ {
+ Code_ = code;
+ }
+
+ const TString& GetMessage() const noexcept
+ {
+ return Message_;
+ }
+
+ TString* MutableMessage() noexcept
+ {
+ return &Message_;
+ }
+
+ bool HasOriginAttributes() const noexcept
+ {
+ return OriginAttributes_.ThreadName.Length > 0;
+ }
+
+ const TOriginAttributes& OriginAttributes() const noexcept
+ {
+ return OriginAttributes_;
+ }
+
+ TOriginAttributes* MutableOriginAttributes() noexcept
+ {
+ return &OriginAttributes_;
+ }
+
+ TProcessId GetPid() const noexcept
+ {
+ return OriginAttributes_.Pid;
+ }
+
+ NThreading::TThreadId GetTid() const noexcept
+ {
+ return OriginAttributes_.Tid;
+ }
+
+ TStringBuf GetThreadName() const noexcept
+ {
+ return OriginAttributes_.ThreadName.ToStringBuf();
+ }
+
+ bool HasDatetime() const noexcept
+ {
+ return OriginAttributes_.Datetime != TInstant();
+ }
+
+ TInstant GetDatetime() const noexcept
+ {
+ return OriginAttributes_.Datetime;
+ }
+
+ void SetDatetime(TInstant datetime) noexcept
+ {
+ OriginAttributes_.Datetime = datetime;
+ }
+
+ bool HasAttributes() const noexcept
+ {
+ return Attributes_.operator bool();
+ }
+
+ const IAttributeDictionary& Attributes() const
+ {
+ if (!Attributes_) {
+ return EmptyAttributes();
+ }
+ return *Attributes_;
+ }
+
+ void SetAttributes(IAttributeDictionaryPtr attributes)
+ {
+ Attributes_ = std::move(attributes);
+ ExtractBultinAttributes();
+ }
+
+ IAttributeDictionary* MutableAttributes() noexcept
+ {
+ if (!Attributes_) {
+ Attributes_ = CreateEphemeralAttributes();
+ }
+ return Attributes_.Get();
+ }
+
+ const std::vector<TError>& InnerErrors() const noexcept
+ {
+ return InnerErrors_;
+ }
+
+ std::vector<TError>* MutableInnerErrors() noexcept
+ {
+ return &InnerErrors_;
+ }
+
+private:
+ TErrorCode Code_;
+ TString Message_;
+
+ TOriginAttributes OriginAttributes_{
+ .Pid = 0,
+ .Tid = NThreading::InvalidThreadId,
+ };
+
+ NYTree::IAttributeDictionaryPtr Attributes_;
+ std::vector<TError> InnerErrors_;
+
+ void ExtractBultinAttributes()
+ {
+ OriginAttributes_ = NYT::NDetail::ExtractFromDictionary(Attributes_);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+bool IsWhitelisted(const TError& error, const THashSet<TStringBuf>& attributeWhitelist)
+{
+ for (const auto& key : error.Attributes().ListKeys()) {
+ if (attributeWhitelist.contains(key)) {
+ return true;
+ }
+ }
+
+ for (const auto& innerError : error.InnerErrors()) {
+ if (IsWhitelisted(innerError, attributeWhitelist)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//! Returns vector which consists of objects from errors such that:
+//! if N is the number of objects in errors s.t. IsWhitelisted is true
+//! then first N objects of returned vector are the ones for which IsWhitelisted is true
+//! followed by std::max(0, maxInnerErrorCount - N - 1) remaining objects
+//! finally followed by errors.back().
+std::vector<TError>& ApplyWhitelist(std::vector<TError>& errors, const THashSet<TStringBuf>& attributeWhitelist, int maxInnerErrorCount)
+{
+ if (std::ssize(errors) < std::max(2, maxInnerErrorCount)) {
+ return errors;
+ }
+
+ auto firstNotWhitelisted = std::partition(
+ errors.begin(),
+ std::prev(errors.end()),
+ [&attributeWhitelist] (const TError& error) {
+ return IsWhitelisted(error, attributeWhitelist);
+ });
+
+ int lastErrorOffset = std::max<int>(firstNotWhitelisted - errors.begin(), maxInnerErrorCount - 1);
+
+ *(errors.begin() + lastErrorOffset) = std::move(errors.back());
+ errors.resize(lastErrorOffset + 1);
+
+ return errors;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+TError::TErrorOr() = default;
+
+TError::~TErrorOr() = default;
+
+TError::TErrorOr(const TError& other)
+{
+ if (!other.IsOK()) {
+ Impl_ = std::make_unique<TImpl>(*other.Impl_);
+ }
+}
+
+TError::TErrorOr(TError&& other) noexcept
+ : Impl_(std::move(other.Impl_))
+{ }
+
+TError::TErrorOr(const std::exception& ex)
+{
+ if (auto simpleException = dynamic_cast<const TSimpleException*>(&ex)) {
+ *this = TError(NYT::EErrorCode::Generic, TRuntimeFormat{simpleException->GetMessage()});
+ // NB: clang-14 is incapable of capturing structured binding variables
+ // so we force materialize them via this function call.
+ auto addAttribute = [this] (const auto& key, const auto& value) {
+ std::visit([&] (const auto& actual) {
+ *this <<= TErrorAttribute(key, actual);
+ }, value);
+ };
+ for (const auto& [key, value] : simpleException->GetAttributes()) {
+ addAttribute(key, value);
+ }
+ try {
+ if (simpleException->GetInnerException()) {
+ std::rethrow_exception(simpleException->GetInnerException());
+ }
+ } catch (const std::exception& innerEx) {
+ *this <<= TError(innerEx);
+ }
+ } else if (const auto* errorEx = dynamic_cast<const TErrorException*>(&ex)) {
+ *this = errorEx->Error();
+ } else {
+ *this = TError(NYT::EErrorCode::Generic, TRuntimeFormat{ex.what()});
+ }
+ YT_VERIFY(!IsOK());
+}
+
+TError::TErrorOr(TString message, TDisableFormat)
+ : Impl_(std::make_unique<TImpl>(std::move(message)))
+{ }
+
+TError::TErrorOr(TErrorCode code, TString message, TDisableFormat)
+ : Impl_(std::make_unique<TImpl>(code, std::move(message)))
+{ }
+
+TError& TError::operator = (const TError& other)
+{
+ if (other.IsOK()) {
+ Impl_.reset();
+ } else {
+ Impl_ = std::make_unique<TImpl>(*other.Impl_);
+ }
+ return *this;
+}
+
+TError& TError::operator = (TError&& other) noexcept
+{
+ Impl_ = std::move(other.Impl_);
+ return *this;
+}
+
+TError TError::FromSystem()
+{
+ return FromSystem(LastSystemError());
+}
+
+TError TError::FromSystem(int error)
+{
+ return TError(TErrorCode(LinuxErrorCodeBase + error), TRuntimeFormat{LastSystemErrorText(error)}) <<
+ TErrorAttribute("errno", error);
+}
+
+TError TError::FromSystem(const TSystemError& error)
+{
+ return FromSystem(error.Status());
+}
+
+TErrorCode TError::GetCode() const
+{
+ if (!Impl_) {
+ return NYT::EErrorCode::OK;
+ }
+ return Impl_->GetCode();
+}
+
+TError& TError::SetCode(TErrorCode code)
+{
+ MakeMutable();
+ Impl_->SetCode(code);
+ return *this;
+}
+
+TErrorCode TError::GetNonTrivialCode() const
+{
+ if (!Impl_) {
+ return NYT::EErrorCode::OK;
+ }
+
+ if (GetCode() != NYT::EErrorCode::Generic) {
+ return GetCode();
+ }
+
+ for (const auto& innerError : InnerErrors()) {
+ auto innerCode = innerError.GetNonTrivialCode();
+ if (innerCode != NYT::EErrorCode::Generic) {
+ return innerCode;
+ }
+ }
+
+ return GetCode();
+}
+
+THashSet<TErrorCode> TError::GetDistinctNonTrivialErrorCodes() const
+{
+ THashSet<TErrorCode> result;
+ TraverseError(*this, [&result] (const TError& error, int /*depth*/) {
+ if (auto errorCode = error.GetCode(); errorCode != NYT::EErrorCode::OK) {
+ result.insert(errorCode);
+ }
+ });
+ return result;
+}
+
+const TString& TError::GetMessage() const
+{
+ if (!Impl_) {
+ static const TString Result;
+ return Result;
+ }
+ return Impl_->GetMessage();
+}
+
+TError& TError::SetMessage(TString message)
+{
+ MakeMutable();
+ *Impl_->MutableMessage() = std::move(message);
+ return *this;
+}
+
+bool TError::HasOriginAttributes() const
+{
+ if (!Impl_) {
+ return false;
+ }
+ return Impl_->HasOriginAttributes();
+}
+
+bool TError::HasDatetime() const
+{
+ if (!Impl_) {
+ return false;
+ }
+ return Impl_->HasDatetime();
+}
+
+TInstant TError::GetDatetime() const
+{
+ if (!Impl_) {
+ return {};
+ }
+ return Impl_->GetDatetime();
+}
+
+TProcessId TError::GetPid() const
+{
+ if (!Impl_) {
+ return 0;
+ }
+ return Impl_->GetPid();
+}
+
+NThreading::TThreadId TError::GetTid() const
+{
+ if (!Impl_) {
+ return NThreading::InvalidThreadId;
+ }
+ return Impl_->GetTid();
+}
+
+TStringBuf TError::GetThreadName() const
+{
+ if (!Impl_) {
+ static TString empty;
+ return empty;
+ }
+ return Impl_->GetThreadName();
+}
+
+bool TError::HasAttributes() const noexcept
+{
+ if (!Impl_) {
+ return false;
+ }
+ return Impl_->HasAttributes();
+}
+
+const IAttributeDictionary& TError::Attributes() const
+{
+ if (!Impl_) {
+ return EmptyAttributes();
+ }
+ return Impl_->Attributes();
+}
+
+IAttributeDictionary* TError::MutableAttributes()
+{
+ MakeMutable();
+ return Impl_->MutableAttributes();
+}
+
+const std::vector<TError>& TError::InnerErrors() const
+{
+ if (!Impl_) {
+ static const std::vector<TError> Result;
+ return Result;
+ }
+ return Impl_->InnerErrors();
+}
+
+std::vector<TError>* TError::MutableInnerErrors()
+{
+ MakeMutable();
+ return Impl_->MutableInnerErrors();
+}
+
+TOriginAttributes* TError::MutableOriginAttributes() const noexcept
+{
+ if (!Impl_) {
+ return nullptr;
+ }
+ return Impl_->MutableOriginAttributes();
+}
+
+void TError::SetAttributes(NYTree::IAttributeDictionaryPtr attributes) noexcept
+{
+ if (!Impl_) {
+ return;
+ }
+ Impl_->SetAttributes(std::move(attributes));
+}
+
+const TString InnerErrorsTruncatedKey("inner_errors_truncated");
+
+TError TError::Truncate(
+ int maxInnerErrorCount,
+ i64 stringLimit,
+ const THashSet<TStringBuf>& attributeWhitelist) const &
+{
+ if (!Impl_) {
+ return TError();
+ }
+
+ auto truncateInnerError = [=, &attributeWhitelist] (const TError& innerError) {
+ return innerError.Truncate(maxInnerErrorCount, stringLimit, attributeWhitelist);
+ };
+
+ auto truncateAttributes = [stringLimit, &attributeWhitelist] (const IAttributeDictionary& attributes) {
+ auto truncatedAttributes = CreateEphemeralAttributes();
+ for (const auto& key : attributes.ListKeys()) {
+ const auto& value = attributes.FindYson(key);
+
+ if (std::ssize(value.AsStringBuf()) > stringLimit && !attributeWhitelist.contains(key)) {
+ truncatedAttributes->SetYson(
+ key,
+ NYson::ConvertToYsonString("...<attribute truncated>..."));
+ } else {
+ truncatedAttributes->SetYson(
+ key,
+ value);
+ }
+ }
+ return truncatedAttributes;
+ };
+
+ auto result = std::make_unique<TImpl>();
+ result->SetCode(GetCode());
+ *result->MutableMessage()
+ = std::move(TruncateString(GetMessage(), stringLimit, ErrorMessageTruncatedSuffix));
+ if (Impl_->HasAttributes()) {
+ result->SetAttributes(truncateAttributes(Impl_->Attributes()));
+ }
+ *result->MutableOriginAttributes() = Impl_->OriginAttributes();
+
+ const auto& innerErrors = InnerErrors();
+ auto& copiedInnerErrors = *result->MutableInnerErrors();
+
+ if (std::ssize(innerErrors) <= maxInnerErrorCount) {
+ for (const auto& innerError : innerErrors) {
+ copiedInnerErrors.push_back(truncateInnerError(innerError));
+ }
+ } else {
+ result->MutableAttributes()->Set(InnerErrorsTruncatedKey, true);
+
+ // NB(arkady-e1ppa): We want to always keep the last inner error,
+ // so we make room for it and do not check if it is whitelisted.
+ for (int idx = 0; idx < std::ssize(innerErrors) - 1; ++idx) {
+ const auto& innerError = innerErrors[idx];
+ if (
+ IsWhitelisted(innerError, attributeWhitelist) ||
+ std::ssize(copiedInnerErrors) < maxInnerErrorCount - 1)
+ {
+ copiedInnerErrors.push_back(truncateInnerError(innerError));
+ }
+ }
+ copiedInnerErrors.push_back(truncateInnerError(innerErrors.back()));
+ }
+
+ return TError(std::move(result));
+}
+
+TError TError::Truncate(
+ int maxInnerErrorCount,
+ i64 stringLimit,
+ const THashSet<TStringBuf>& attributeWhitelist) &&
+{
+ if (!Impl_) {
+ return TError();
+ }
+
+ auto truncateInnerError = [=, &attributeWhitelist] (TError& innerError) {
+ innerError = std::move(innerError).Truncate(maxInnerErrorCount, stringLimit, attributeWhitelist);
+ };
+
+ auto truncateAttributes = [stringLimit, &attributeWhitelist] (IAttributeDictionary* attributes) {
+ for (const auto& key : attributes->ListKeys()) {
+ if (std::ssize(attributes->FindYson(key).AsStringBuf()) > stringLimit && !attributeWhitelist.contains(key)) {
+ attributes->SetYson(
+ key,
+ NYson::ConvertToYsonString("...<attribute truncated>..."));
+ }
+ }
+ };
+
+ TruncateStringInplace(Impl_->MutableMessage(), stringLimit, ErrorMessageTruncatedSuffix);
+ if (Impl_->HasAttributes()) {
+ truncateAttributes(Impl_->MutableAttributes());
+ }
+ if (std::ssize(InnerErrors()) <= maxInnerErrorCount) {
+ for (auto& innerError : *MutableInnerErrors()) {
+ truncateInnerError(innerError);
+ }
+ } else {
+ auto& innerErrors = ApplyWhitelist(*MutableInnerErrors(), attributeWhitelist, maxInnerErrorCount);
+ MutableAttributes()->Set(InnerErrorsTruncatedKey, true);
+
+ for (auto& innerError : innerErrors) {
+ truncateInnerError(innerError);
+ }
+ }
+
+ return std::move(*this);
+}
+
+bool TError::IsOK() const
+{
+ if (!Impl_) {
+ return true;
+ }
+ return Impl_->IsOK();
+}
+
+TError TError::Wrap() const &
+{
+ return *this;
+}
+
+TError TError::Wrap() &&
+{
+ return std::move(*this);
+}
+
+Y_WEAK TString GetErrorSkeleton(const TError& /*error*/)
+{
+ // Proper implementation resides in yt/yt/library/error_skeleton/skeleton.cpp.
+ THROW_ERROR_EXCEPTION("Error skeleton implementation library is not linked; consider PEERDIR'ing yt/yt/library/error_skeleton");
+}
+
+TString TError::GetSkeleton() const
+{
+ return GetErrorSkeleton(*this);
+}
+
+std::optional<TError> TError::FindMatching(TErrorCode code) const
+{
+ return FindMatching([&] (TErrorCode errorCode) {
+ return code == errorCode;
+ });
+}
+
+std::optional<TError> TError::FindMatching(const THashSet<TErrorCode>& codes) const
+{
+ return FindMatching([&] (TErrorCode code) {
+ return codes.contains(code);
+ });
+}
+
+TError::TErrorOr(std::unique_ptr<TImpl> impl)
+ : Impl_(std::move(impl))
+{ }
+
+void TError::MakeMutable()
+{
+ if (!Impl_) {
+ Impl_ = std::make_unique<TImpl>();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TError& TError::operator <<= (const TErrorAttribute& attribute) &
+{
+ MutableAttributes()->SetYson(attribute.Key, attribute.Value);
+ return *this;
+}
+
+TError& TError::operator <<= (const std::vector<TErrorAttribute>& attributes) &
+{
+ for (const auto& attribute : attributes) {
+ MutableAttributes()->SetYson(attribute.Key, attribute.Value);
+ }
+ return *this;
+}
+
+TError& TError::operator <<= (const TError& innerError) &
+{
+ MutableInnerErrors()->push_back(innerError);
+ return *this;
+}
+
+TError& TError::operator <<= (TError&& innerError) &
+{
+ MutableInnerErrors()->push_back(std::move(innerError));
+ return *this;
+}
+
+TError& TError::operator <<= (const std::vector<TError>& innerErrors) &
+{
+ MutableInnerErrors()->insert(
+ MutableInnerErrors()->end(),
+ innerErrors.begin(),
+ innerErrors.end());
+ return *this;
+}
+
+TError& TError::operator <<= (std::vector<TError>&& innerErrors) &
+{
+ MutableInnerErrors()->insert(
+ MutableInnerErrors()->end(),
+ std::make_move_iterator(innerErrors.begin()),
+ std::make_move_iterator(innerErrors.end()));
+ return *this;
+}
+
+TError& TError::operator <<= (const NYTree::IAttributeDictionary& attributes) &
+{
+ MutableAttributes()->MergeFrom(attributes);
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator == (const TError& lhs, const TError& rhs)
+{
+ if (!lhs.MutableOriginAttributes() && !rhs.MutableOriginAttributes()) {
+ return true;
+ }
+ // NB(arkady-e1ppa): Origin attributes equality comparison is
+ // bit-wise but garbage bits are zeroed so it shouldn't matter.
+ return
+ lhs.GetCode() == rhs.GetCode() &&
+ lhs.GetMessage() == rhs.GetMessage() &&
+ *lhs.MutableOriginAttributes() == *rhs.MutableOriginAttributes() &&
+ lhs.Attributes() == rhs.Attributes() &&
+ lhs.InnerErrors() == rhs.InnerErrors();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void AppendIndent(TStringBuilderBase* builer, int indent)
+{
+ builer->AppendChar(' ', indent);
+}
+
+void AppendAttribute(TStringBuilderBase* builder, const TString& key, const TString& value, int indent)
+{
+ AppendIndent(builder, indent + 4);
+ if (!value.Contains('\n')) {
+ builder->AppendFormat("%-15s %s", key, value);
+ } else {
+ builder->AppendString(key);
+ TString indentedValue = "\n" + value;
+ SubstGlobal(indentedValue, "\n", "\n" + TString{static_cast<size_t>(indent + 8), ' '});
+ // Now first line in indentedValue is empty and every other line is indented by 8 spaces.
+ builder->AppendString(indentedValue);
+ }
+ builder->AppendChar('\n');
+}
+
+void AppendError(TStringBuilderBase* builder, const TError& error, int indent)
+{
+ if (error.IsOK()) {
+ builder->AppendString("OK");
+ return;
+ }
+
+ AppendIndent(builder, indent);
+ builder->AppendString(error.GetMessage());
+ builder->AppendChar('\n');
+
+ if (error.GetCode() != NYT::EErrorCode::Generic) {
+ AppendAttribute(builder, "code", ToString(static_cast<int>(error.GetCode())), indent);
+ }
+
+ // Pretty-print origin.
+ if (error.HasOriginAttributes()) {
+ AppendAttribute(
+ builder,
+ "origin",
+ NYT::NDetail::FormatOrigin(*error.MutableOriginAttributes()),
+ indent);
+ } else if (IsErrorSanitizerEnabled() && HasHost(error)) {
+ AppendAttribute(
+ builder,
+ "host",
+ ToString(GetHost(error)),
+ indent);
+ }
+
+ if (error.HasDatetime()) {
+ AppendAttribute(
+ builder,
+ "datetime",
+ Format("%v", error.GetDatetime()),
+ indent);
+ }
+
+ for (const auto& [key, value] : error.Attributes().ListPairs()) {
+ TTokenizer tokenizer(value.AsStringBuf());
+ YT_VERIFY(tokenizer.ParseNext());
+ switch (tokenizer.GetCurrentType()) {
+ case ETokenType::String:
+ AppendAttribute(builder, key, TString(tokenizer.CurrentToken().GetStringValue()), indent);
+ break;
+ case ETokenType::Int64:
+ AppendAttribute(builder, key, ToString(tokenizer.CurrentToken().GetInt64Value()), indent);
+ break;
+ case ETokenType::Uint64:
+ AppendAttribute(builder, key, ToString(tokenizer.CurrentToken().GetUint64Value()), indent);
+ break;
+ case ETokenType::Double:
+ AppendAttribute(builder, key, ToString(tokenizer.CurrentToken().GetDoubleValue()), indent);
+ break;
+ case ETokenType::Boolean:
+ AppendAttribute(builder, key, TString(FormatBool(tokenizer.CurrentToken().GetBooleanValue())), indent);
+ break;
+ default:
+ AppendAttribute(builder, key, ConvertToYsonString(value, EYsonFormat::Text).ToString(), indent);
+ break;
+ }
+ }
+
+ for (const auto& innerError : error.InnerErrors()) {
+ builder->AppendChar('\n');
+ AppendError(builder, innerError, indent + 2);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, const TError& error, TStringBuf /*spec*/)
+{
+ AppendError(builder, error, 0);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TraverseError(const TError& error, const TErrorVisitor& visitor, int depth)
+{
+ visitor(error, depth);
+ for (const auto& inner : error.InnerErrors()) {
+ TraverseError(inner, visitor, depth + 1);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const char* TErrorException::what() const noexcept
+{
+ if (CachedWhat_.empty()) {
+ CachedWhat_ = ToString(Error_);
+ }
+ return CachedWhat_.data();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/yt/yt/core/misc/stripped_error.h b/yt/yt/core/misc/stripped_error.h
new file mode 100644
index 0000000000..aea3077e63
--- /dev/null
+++ b/yt/yt/core/misc/stripped_error.h
@@ -0,0 +1,458 @@
+#pragma once
+
+#include "public.h"
+
+#include <yt/yt/core/ytree/public.h>
+
+#include <library/cpp/yt/threading/public.h>
+
+#include <library/cpp/yt/error/origin_attributes.h>
+
+#include <library/cpp/yt/yson/public.h>
+
+#include <library/cpp/yt/yson_string/convert.h>
+#include <library/cpp/yt/yson_string/string.h>
+
+#include <library/cpp/yt/misc/property.h>
+
+#include <util/system/compiler.h>
+#include <util/system/getpid.h>
+
+#include <util/generic/size_literals.h>
+
+#include <type_traits>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! An opaque wrapper around |int| value capable of conversions from |int|s and
+//! arbitrary enum types.
+class TErrorCode
+{
+public:
+ using TUnderlying = int;
+
+ constexpr TErrorCode();
+ explicit constexpr TErrorCode(int value);
+ template <class E>
+ requires std::is_enum_v<E>
+ constexpr TErrorCode(E value);
+
+ constexpr operator int() const;
+
+ template <class E>
+ requires std::is_enum_v<E>
+ constexpr operator E() const;
+
+ template <class E>
+ requires std::is_enum_v<E>
+ constexpr bool operator == (E rhs) const;
+
+ constexpr bool operator == (TErrorCode rhs) const;
+private:
+ int Value_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, TErrorCode code, TStringBuf spec);
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct TErrorAttribute
+{
+ template <class T>
+ TErrorAttribute(const TString& key, const T& value)
+ : Key(key)
+ , Value(NYson::ConvertToYsonString(value))
+ { }
+
+ TErrorAttribute(const TString& key, const NYson::TYsonString& value)
+ : Key(key)
+ , Value(value)
+ { }
+
+ TString Key;
+ NYson::TYsonString Value;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TValue>
+concept CErrorNestable = requires (TError& error, TValue&& operand)
+{
+ { error <<= std::forward<TValue>(operand) } -> std::same_as<TError&>;
+};
+
+template <>
+class [[nodiscard]] TErrorOr<void>
+{
+public:
+ TErrorOr();
+ ~TErrorOr();
+
+ TErrorOr(const TError& other);
+ TErrorOr(TError&& other) noexcept;
+
+ TErrorOr(const std::exception& ex);
+
+ struct TDisableFormat
+ { };
+ static constexpr TDisableFormat DisableFormat = {};
+
+ TErrorOr(TString message, TDisableFormat);
+ TErrorOr(TErrorCode code, TString message, TDisableFormat);
+
+ template <class... TArgs>
+ explicit TErrorOr(
+ TFormatString<TArgs...> format,
+ TArgs&&... arg);
+
+ template <class... TArgs>
+ TErrorOr(
+ TErrorCode code,
+ TFormatString<TArgs...> format,
+ TArgs&&... args);
+
+ TError& operator = (const TError& other);
+ TError& operator = (TError&& other) noexcept;
+
+ static TError FromSystem();
+ static TError FromSystem(int error);
+ static TError FromSystem(const TSystemError& error);
+
+ TErrorCode GetCode() const;
+ TError& SetCode(TErrorCode code);
+
+ TErrorCode GetNonTrivialCode() const;
+ THashSet<TErrorCode> GetDistinctNonTrivialErrorCodes() const;
+
+ const TString& GetMessage() const;
+ TError& SetMessage(TString message);
+
+ bool HasOriginAttributes() const;
+ TProcessId GetPid() const;
+ TStringBuf GetThreadName() const;
+ NThreading::TThreadId GetTid() const;
+
+ bool HasDatetime() const;
+ TInstant GetDatetime() const;
+
+ bool HasAttributes() const noexcept;
+
+ const NYTree::IAttributeDictionary& Attributes() const;
+ NYTree::IAttributeDictionary* MutableAttributes();
+
+ const std::vector<TError>& InnerErrors() const;
+ std::vector<TError>* MutableInnerErrors();
+
+ // Used for deserialization only.
+ TOriginAttributes* MutableOriginAttributes() const noexcept;
+ void SetAttributes(NYTree::IAttributeDictionaryPtr attributes) noexcept;
+
+ TError Truncate(
+ int maxInnerErrorCount = 2,
+ i64 stringLimit = 16_KB,
+ const THashSet<TStringBuf>& attributeWhitelist = {}) const &;
+ TError Truncate(
+ int maxInnerErrorCount = 2,
+ i64 stringLimit = 16_KB,
+ const THashSet<TStringBuf>& attributeWhitelist = {}) &&;
+
+ bool IsOK() const;
+
+ template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+ void ThrowOnError(U&& u) const &;
+ template <class... TArgs>
+ void ThrowOnError(TFormatString<TArgs...> format, TArgs&&... args) const &;
+ template <class... TArgs>
+ void ThrowOnError(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) const &;
+ inline void ThrowOnError() const &;
+
+ template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+ void ThrowOnError(U&& u) &&;
+ template <class... TArgs>
+ void ThrowOnError(TFormatString<TArgs...> format, TArgs&&... args) &&;
+ template <class... TArgs>
+ void ThrowOnError(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) &&;
+ inline void ThrowOnError() &&;
+
+ template <CInvocable<bool(const TError&)> TFilter>
+ std::optional<TError> FindMatching(const TFilter& filter) const;
+ template <CInvocable<bool(TErrorCode)> TFilter>
+ std::optional<TError> FindMatching(const TFilter& filter) const;
+ std::optional<TError> FindMatching(TErrorCode code) const;
+ std::optional<TError> FindMatching(const THashSet<TErrorCode>& codes) const;
+
+ template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+ TError Wrap(U&& u) const &;
+ template <class... TArgs>
+ TError Wrap(TFormatString<TArgs...> format, TArgs&&... args) const &;
+ template <class... TArgs>
+ TError Wrap(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) const &;
+ TError Wrap() const &;
+
+ template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+ TError Wrap(U&& u) &&;
+ template <class... TArgs>
+ TError Wrap(TFormatString<TArgs...> format, TArgs&&... args) &&;
+ template <class... TArgs>
+ TError Wrap(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) &&;
+ TError Wrap() &&;
+
+ //! Perform recursive aggregation of error codes and messages over the error tree.
+ //! Result of this aggregation is suitable for error clustering in groups of
+ //! "similar" errors. Refer to yt/yt/library/error_skeleton/skeleton_ut.cpp for examples.
+ //!
+ //! This method builds skeleton from scratch by doing complete error tree traversal,
+ //! so calling it in computationally hot code is discouraged.
+ //!
+ //! In order to prevent core -> re2 dependency, implementation belongs to a separate library
+ //! yt/yt/library/error_skeleton. Calling this method without PEERDIR'ing implementation
+ //! results in an exception.
+ TString GetSkeleton() const;
+
+ TError& operator <<= (const TErrorAttribute& attribute) &;
+ TError& operator <<= (const std::vector<TErrorAttribute>& attributes) &;
+ TError& operator <<= (const TError& innerError) &;
+ TError& operator <<= (TError&& innerError) &;
+ TError& operator <<= (const std::vector<TError>& innerErrors) &;
+ TError& operator <<= (std::vector<TError>&& innerErrors) &;
+ TError& operator <<= (const NYTree::IAttributeDictionary& attributes) &;
+
+ template <CErrorNestable TValue>
+ TError&& operator << (TValue&& operand) &&;
+
+ template <CErrorNestable TValue>
+ TError operator << (TValue&& operand) const &;
+
+ template <CErrorNestable TValue>
+ TError&& operator << (const std::optional<TValue>& rhs) &&;
+
+ template <CErrorNestable TValue>
+ TError operator << (const std::optional<TValue>& rhs) const &;
+
+private:
+ class TImpl;
+ std::unique_ptr<TImpl> Impl_;
+
+ explicit TErrorOr(std::unique_ptr<TImpl> impl);
+
+ void MakeMutable();
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator == (const TError& lhs, const TError& rhs);
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, const TError& error, TStringBuf spec);
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TErrorVisitor = std::function<void(const TError&, int depth)>;
+
+//! Traverses the error tree in DFS order.
+void TraverseError(
+ const TError& error,
+ const TErrorVisitor& visitor,
+ int depth = 0);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+struct TErrorTraits
+{
+ using TWrapped = TErrorOr<T>;
+ using TUnwrapped = T;
+};
+
+template <class T>
+struct TErrorTraits<TErrorOr<T>>
+{
+ using TUnderlying = T;
+ using TWrapped = TErrorOr<T>;
+ using TUnwrapped = T;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TErrorException
+ : public std::exception
+{
+public:
+ DEFINE_BYREF_RW_PROPERTY(TError, Error);
+
+public:
+ TErrorException() = default;
+ TErrorException(const TErrorException& other) = default;
+ TErrorException(TErrorException&& other) = default;
+
+ const char* what() const noexcept override;
+
+private:
+ mutable TString CachedWhat_;
+};
+
+// Make these templates to avoid type erasure during throw.
+template <class TException>
+ requires std::derived_from<std::remove_cvref_t<TException>, TErrorException>
+TException&& operator <<= (TException&& ex, const TError& error);
+template <class TException>
+ requires std::derived_from<std::remove_cvref_t<TException>, TErrorException>
+TException&& operator <<= (TException&& ex, TError&& error);
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+struct TErrorAdaptor
+{
+ template <class TArg>
+ requires std::constructible_from<TError, TArg>
+ TError operator << (TArg&& rhs) const;
+
+ template <class TArg>
+ requires
+ std::constructible_from<TError, TArg> &&
+ std::derived_from<std::remove_cvref_t<TArg>, TError>
+ TArg&& operator << (TArg&& rhs) const;
+};
+
+// Make these to correctly forward TError to Wrap call.
+template <class TErrorLike, class U>
+ requires
+ std::derived_from<std::remove_cvref_t<TErrorLike>, TError> &&
+ (!CStringLiteral<std::remove_cvref_t<U>>)
+void ThrowErrorExceptionIfFailed(TErrorLike&& error, U&& u);
+
+template <class TErrorLike, class... TArgs>
+ requires std::derived_from<std::remove_cvref_t<TErrorLike>, TError>
+void ThrowErrorExceptionIfFailed(TErrorLike&& error, TFormatString<TArgs...> format, TArgs&&... args);
+
+template <class TErrorLike, class... TArgs>
+ requires std::derived_from<std::remove_cvref_t<TErrorLike>, TError>
+void ThrowErrorExceptionIfFailed(TErrorLike&& error, TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args);
+
+template <class TErrorLike>
+ requires std::derived_from<std::remove_cvref_t<TErrorLike>, TError>
+void ThrowErrorExceptionIfFailed(TErrorLike&& error);
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define THROW_ERROR \
+ throw ::NYT::TErrorException() <<= ::NYT::NDetail::TErrorAdaptor() <<
+
+#define THROW_ERROR_EXCEPTION(head, ...) \
+ THROW_ERROR ::NYT::TError(head __VA_OPT__(,) __VA_ARGS__)
+
+#define THROW_ERROR_EXCEPTION_IF_FAILED(error, ...) \
+ ::NYT::NDetail::ThrowErrorExceptionIfFailed((error) __VA_OPT__(,) __VA_ARGS__); \
+
+#define THROW_ERROR_EXCEPTION_UNLESS(condition, head, ...) \
+ if ((condition)) {\
+ } else { \
+ THROW_ERROR ::NYT::TError(head __VA_OPT__(,) __VA_ARGS__); \
+ }
+
+#define THROW_ERROR_EXCEPTION_IF(condition, head, ...) \
+ THROW_ERROR_EXCEPTION_UNLESS(!(condition), head, __VA_ARGS__)
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+class [[nodiscard]] TErrorOr
+ : public TError
+{
+public:
+ TErrorOr();
+
+ TErrorOr(const T& value);
+ TErrorOr(T&& value) noexcept;
+
+ TErrorOr(const TErrorOr<T>& other);
+ TErrorOr(TErrorOr<T>&& other) noexcept;
+
+ TErrorOr(const TError& other);
+ TErrorOr(TError&& other) noexcept;
+
+ TErrorOr(const std::exception& ex);
+
+ template <class U>
+ TErrorOr(const TErrorOr<U>& other);
+ template <class U>
+ TErrorOr(TErrorOr<U>&& other) noexcept;
+
+ TErrorOr<T>& operator = (const TErrorOr<T>& other)
+ requires std::is_copy_assignable_v<T>;
+ TErrorOr<T>& operator = (TErrorOr<T>&& other) noexcept
+ requires std::is_nothrow_move_assignable_v<T>;
+
+ const T& Value() const & Y_LIFETIME_BOUND;
+ T& Value() & Y_LIFETIME_BOUND;
+ T&& Value() && Y_LIFETIME_BOUND;
+
+ template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+ const T& ValueOrThrow(U&& u) const & Y_LIFETIME_BOUND;
+ template <class... TArgs>
+ const T& ValueOrThrow(TFormatString<TArgs...> format, TArgs&&... args) const & Y_LIFETIME_BOUND;
+ template <class... TArgs>
+ const T& ValueOrThrow(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) const & Y_LIFETIME_BOUND;
+ const T& ValueOrThrow() const & Y_LIFETIME_BOUND;
+
+ template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+ T& ValueOrThrow(U&& u) & Y_LIFETIME_BOUND;
+ template <class... TArgs>
+ T& ValueOrThrow(TFormatString<TArgs...> format, TArgs&&... args) & Y_LIFETIME_BOUND;
+ template <class... TArgs>
+ T& ValueOrThrow(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) & Y_LIFETIME_BOUND;
+ T& ValueOrThrow() & Y_LIFETIME_BOUND;
+
+ template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+ T&& ValueOrThrow(U&& u) && Y_LIFETIME_BOUND;
+ template <class... TArgs>
+ T&& ValueOrThrow(TFormatString<TArgs...> format, TArgs&&... args) && Y_LIFETIME_BOUND;
+ template <class... TArgs>
+ T&& ValueOrThrow(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) && Y_LIFETIME_BOUND;
+ T&& ValueOrThrow() && Y_LIFETIME_BOUND;
+
+ const T& ValueOrDefault(const T& defaultValue Y_LIFETIME_BOUND) const & Y_LIFETIME_BOUND;
+ T& ValueOrDefault(T& defaultValue Y_LIFETIME_BOUND) & Y_LIFETIME_BOUND;
+ constexpr T ValueOrDefault(T&& defaultValue) const &;
+ constexpr T ValueOrDefault(T&& defaultValue) &&;
+
+private:
+ std::optional<T> Value_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+void FormatValue(TStringBuilderBase* builder, const TErrorOr<T>& error, TStringBuf spec);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class F, class... As>
+auto RunNoExcept(F&& functor, As&&... args) noexcept -> decltype(functor(std::forward<As>(args)...))
+{
+ return functor(std::forward<As>(args)...);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+#define STRIPPED_ERROR_INL_H_
+#include "stripped_error-inl.h"
+#undef STRIPPED_ERROR_INL_H_
diff --git a/yt/yt/core/misc/unittests/error_ut.cpp b/yt/yt/core/misc/unittests/error_ut.cpp
index 42e731234a..09e5a0fb2f 100644
--- a/yt/yt/core/misc/unittests/error_ut.cpp
+++ b/yt/yt/core/misc/unittests/error_ut.cpp
@@ -1,9 +1,15 @@
-#include "library/cpp/testing/gtest_extensions/assertions.h"
#include <yt/yt/core/test_framework/framework.h>
+#include <yt/yt/core/concurrency/action_queue.h>
+#include <yt/yt/core/concurrency/scheduler.h>
+
#include <yt/yt/core/misc/error.h>
#include <yt/yt/core/misc/error_helpers.h>
+#include <yt/yt/core/net/local_address.h>
+
+#include <yt/yt/core/tracing/trace_context.h>
+
#include <yt/yt/core/yson/string.h>
#include <yt/yt/core/ytree/attributes.h>
@@ -309,18 +315,18 @@ TEST(TErrorTest, WrapRValue)
{
TError error("Error");
- TError errorCopy = error;
- auto wrapped = std::move(errorCopy).Wrap("Wrapped error");
- EXPECT_TRUE(errorCopy.IsOK());
- EXPECT_EQ(wrapped.GetCode(), NYT::EErrorCode::Generic);
- EXPECT_EQ(wrapped.GetMessage(), "Wrapped error");
- EXPECT_EQ(wrapped.InnerErrors().size(), 1u);
- EXPECT_EQ(wrapped.InnerErrors()[0], error);
+ // TError errorCopy = error;
+ // auto wrapped = std::move(errorCopy).Wrap("Wrapped error");
+ // EXPECT_TRUE(errorCopy.IsOK());
+ // EXPECT_EQ(wrapped.GetCode(), NYT::EErrorCode::Generic);
+ // EXPECT_EQ(wrapped.GetMessage(), "Wrapped error");
+ // EXPECT_EQ(wrapped.InnerErrors().size(), 1u);
+ // EXPECT_EQ(wrapped.InnerErrors()[0], error);
- TError anotherErrorCopy = error;
- auto trviallyWrapped = std::move(anotherErrorCopy).Wrap();
- EXPECT_TRUE(anotherErrorCopy.IsOK());
- EXPECT_EQ(trviallyWrapped, error);
+ // TError anotherErrorCopy = error;
+ // auto trviallyWrapped = std::move(anotherErrorCopy).Wrap();
+ // EXPECT_TRUE(anotherErrorCopy.IsOK());
+ // EXPECT_EQ(trviallyWrapped, error);
}
TEST(TErrorTest, ThrowErrorExceptionIfFailedMacroJustWorks)
@@ -508,7 +514,7 @@ TEST(TErrorTest, TruncateSimple)
EXPECT_EQ(error.GetMessage(), truncatedError.GetMessage());
EXPECT_EQ(error.GetPid(), truncatedError.GetPid());
EXPECT_EQ(error.GetTid(), truncatedError.GetTid());
- EXPECT_EQ(error.GetSpanId(), truncatedError.GetSpanId());
+ EXPECT_EQ(GetSpanId(error), GetSpanId(truncatedError));
EXPECT_EQ(error.GetDatetime(), truncatedError.GetDatetime());
EXPECT_EQ(error.Attributes().Get<TString>("my_attr"), truncatedError.Attributes().Get<TString>("my_attr"));
EXPECT_EQ(error.InnerErrors().size(), truncatedError.InnerErrors().size());
@@ -548,7 +554,7 @@ TEST(TErrorTest, TruncateSimpleRValue)
EXPECT_EQ(error.GetMessage(), truncatedError.GetMessage());
EXPECT_EQ(error.GetPid(), truncatedError.GetPid());
EXPECT_EQ(error.GetTid(), truncatedError.GetTid());
- EXPECT_EQ(error.GetSpanId(), truncatedError.GetSpanId());
+ EXPECT_EQ(GetSpanId(error), GetSpanId(truncatedError));
EXPECT_EQ(error.GetDatetime(), truncatedError.GetDatetime());
EXPECT_EQ(error.Attributes().Get<TString>("my_attr"), truncatedError.Attributes().Get<TString>("my_attr"));
EXPECT_EQ(error.InnerErrors().size(), truncatedError.InnerErrors().size());
@@ -761,20 +767,20 @@ TEST(TErrorTest, ErrorSanitizer)
{
auto checkSantizied = [&] (const TError& error) {
EXPECT_FALSE(error.HasOriginAttributes());
- EXPECT_FALSE(error.HasTracingAttributes());
+ EXPECT_FALSE(HasTracingAttributes(error));
- EXPECT_EQ("<host-override>", error.GetHost());
+ EXPECT_EQ("<host-override>", GetHost(error));
EXPECT_EQ(0, error.GetPid());
EXPECT_EQ(NThreading::InvalidThreadId, error.GetTid());
- EXPECT_EQ(NConcurrency::InvalidFiberId, error.GetFid());
- EXPECT_EQ(NTracing::InvalidTraceId, error.GetTraceId());
- EXPECT_EQ(NTracing::InvalidSpanId, error.GetSpanId());
+ EXPECT_EQ(NConcurrency::InvalidFiberId, GetFid(error));
+ EXPECT_EQ(NTracing::InvalidTraceId, GetTraceId(error));
+ EXPECT_EQ(NTracing::InvalidSpanId, GetSpanId(error));
};
auto checkNotSanitized = [&] (const TError& error) {
EXPECT_TRUE(error.HasOriginAttributes());
- EXPECT_FALSE(error.GetHost() == "<host-override>");
+ EXPECT_FALSE(GetHost(error) == "<host-override>");
EXPECT_FALSE(error.GetPid() == 0);
auto now = TInstant::Now();
@@ -821,11 +827,11 @@ TEST(TErrorTest, SimpleLoadAfterSave)
TStreamSaveContext saveContext(&stream);
TError savedError("error");
- savedError.Save(saveContext);
+ TErrorSerializer::Save(saveContext, savedError);
TStreamLoadContext loadContext(&stream);
TError loadedError;
- loadedError.Load(loadContext);
+ TErrorSerializer::Load(loadContext, loadedError);
EXPECT_EQ(ToString(savedError), ToString(loadedError));
}
@@ -853,6 +859,54 @@ TEST(TErrorTest, AttributeSerialization)
" L3\n"));
}
+TEST(TErrorTest, TraceContext)
+{
+ TError error;
+
+ NTracing::TTraceId traceId;
+ NTracing::TSpanId spanId;
+
+ // NB(arkady-e1ppa): Make sure that tracing data can be decoded
+ // even after traceContext was destroyed.
+ {
+ auto traceContext = NTracing::GetOrCreateTraceContext("Tester");
+ traceId = traceContext->GetTraceId();
+ spanId = traceContext->GetSpanId();
+
+ auto guard = NTracing::TCurrentTraceContextGuard(traceContext);
+
+ error = TError("Capture test");
+ }
+
+ EXPECT_EQ(GetTraceId(error), traceId);
+ EXPECT_EQ(GetSpanId(error), spanId);
+}
+
+TEST(TErrorTest, NativeHostName)
+{
+ auto hostName = "TestHost";
+ NNet::WriteLocalHostName(hostName);
+
+ auto error = TError("NativeHostTest");
+
+ EXPECT_TRUE(HasHost(error));
+ EXPECT_EQ(GetHost(error), TStringBuf(hostName));
+}
+
+TEST(TErrorTest, NativeFiberId)
+{
+ auto actionQueue = New<NConcurrency::TActionQueue>();
+
+ NConcurrency::WaitFor(BIND([] {
+ auto fiberId = NConcurrency::GetCurrentFiberId();
+
+ auto error = TError("TestNativeFiberId");
+
+ EXPECT_EQ(GetFid(error), fiberId);
+ }).AsyncVia(actionQueue->GetInvoker()).Run())
+ .ThrowOnError();
+}
+
TEST(TErrorTest, MacroStaticAnalysis)
{
auto swallow = [] (auto expr) {
diff --git a/yt/yt/core/net/local_address.cpp b/yt/yt/core/net/local_address.cpp
index b862607ccd..03aa4e6650 100644
--- a/yt/yt/core/net/local_address.cpp
+++ b/yt/yt/core/net/local_address.cpp
@@ -50,6 +50,7 @@ std::atomic<bool> IPv6Enabled_ = false;
const char* ReadLocalHostName() noexcept
{
+ NYT::NOrigin::EnableOriginOverrides();
// Writer-side imposes AcqRel ordering, so all preceding writes must be visible.
char* ptr = LocalHostNamePtr.load(std::memory_order::relaxed);
return ptr ? ptr : LocalHostNameData;
@@ -85,6 +86,8 @@ void GuardedWriteString(std::atomic<char*>& storage, char* initial, TStringBuf s
void WriteLocalHostName(TStringBuf hostName) noexcept
{
+ NYT::NOrigin::EnableOriginOverrides();
+
static NThreading::TForkAwareSpinLock Lock;
auto guard = Guard(Lock);
diff --git a/yt/yt/core/rpc/bus/channel.cpp b/yt/yt/core/rpc/bus/channel.cpp
index 2c6643a26e..29746cda72 100644
--- a/yt/yt/core/rpc/bus/channel.cpp
+++ b/yt/yt/core/rpc/bus/channel.cpp
@@ -1103,9 +1103,9 @@ private:
<< TErrorAttribute("timeout", *requestControl->GetTimeout());
}
- if (!detailedError.HasTracingAttributes()) {
+ if (!HasTracingAttributes(detailedError)) {
if (auto tracingAttributes = requestControl->GetTracingAttributes()) {
- detailedError.SetTracingAttributes(*tracingAttributes);
+ SetTracingAttributes(&detailedError, *tracingAttributes);
}
}
diff --git a/yt/yt/core/threading/public.h b/yt/yt/core/threading/public.h
index eb91f06efc..23a190de48 100644
--- a/yt/yt/core/threading/public.h
+++ b/yt/yt/core/threading/public.h
@@ -2,13 +2,12 @@
#include <yt/yt/core/misc/public.h>
+#include <library/cpp/yt/threading/public.h>
+
namespace NYT::NThreading {
////////////////////////////////////////////////////////////////////////////////
-using TThreadId = size_t;
-constexpr size_t InvalidThreadId = 0;
-
DEFINE_ENUM(EThreadPriority,
(Normal)
(RealTime)
diff --git a/yt/yt/core/tracing/trace_context.cpp b/yt/yt/core/tracing/trace_context.cpp
index 8fb08c5dbf..5be2f295db 100644
--- a/yt/yt/core/tracing/trace_context.cpp
+++ b/yt/yt/core/tracing/trace_context.cpp
@@ -110,7 +110,16 @@ TTracingTransportConfigPtr GetTracingTransportConfig()
namespace NDetail {
-YT_DEFINE_THREAD_LOCAL(TTraceContext*, CurrentTraceContext);
+// Expended from YT_DEFINE_THREAD_LOCAL(TTraceContext*, CurrentTraceContext);
+// with Overrides added.
+thread_local TTraceContext *CurrentTraceContextData{};
+YT_PREVENT_TLS_CACHING TTraceContext*& CurrentTraceContext()
+{
+ NYT::NOrigin::EnableOriginOverrides();
+ asm volatile("");
+ return CurrentTraceContextData;
+}
+
YT_DEFINE_THREAD_LOCAL(TCpuInstant, TraceContextTimingCheckpoint);
TSpanId GenerateSpanId()
diff --git a/yt/yt/core/ya.make b/yt/yt/core/ya.make
index d4a54b2ad7..0ce8296a64 100644
--- a/yt/yt/core/ya.make
+++ b/yt/yt/core/ya.make
@@ -135,6 +135,7 @@ SRCS(
misc/linear_probe.cpp
misc/memory_usage_tracker.cpp
misc/relaxed_mpsc_queue.cpp
+ misc/origin_attributes.cpp
misc/parser_helpers.cpp
misc/pattern_formatter.cpp
misc/phoenix.cpp
@@ -155,6 +156,7 @@ SRCS(
misc/statistic_path.cpp
misc/statistics.cpp
misc/string_helpers.cpp
+ misc/stripped_error.cpp
misc/cache_config.cpp
misc/utf8_decoder.cpp
misc/zerocopy_output_writer.cpp
@@ -328,6 +330,8 @@ PEERDIR(
library/cpp/streams/brotli
library/cpp/yt/assert
library/cpp/yt/containers
+ library/cpp/yt/global
+ library/cpp/yt/error
library/cpp/yt/logging
library/cpp/yt/logging/plain_text_formatter
library/cpp/yt/misc