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