diff options
author | innokentii <innokentii@yandex-team.com> | 2022-09-09 13:56:14 +0300 |
---|---|---|
committer | innokentii <innokentii@yandex-team.com> | 2022-09-09 13:56:14 +0300 |
commit | 7cd934b41bf8f1487cbb4eb783a1e7e7e0a8cbdc (patch) | |
tree | 685d3025cdc4b14befe8b9fd9c1448adadd321f9 /library/cpp | |
parent | 695f38b2d33b4c1962bc3ae11737025406116832 (diff) | |
download | ydb-7cd934b41bf8f1487cbb4eb783a1e7e7e0a8cbdc.tar.gz |
Move TSharedData to actors lib
fix MemoryTrack usages
move MemoryTrack to utils
move TSharedData to actors lib
Diffstat (limited to 'library/cpp')
-rw-r--r-- | library/cpp/actors/core/CMakeLists.txt | 2 | ||||
-rw-r--r-- | library/cpp/actors/util/CMakeLists.txt | 3 | ||||
-rw-r--r-- | library/cpp/actors/util/memory_track.cpp (renamed from library/cpp/actors/core/memory_track.cpp) | 0 | ||||
-rw-r--r-- | library/cpp/actors/util/memory_track.h (renamed from library/cpp/actors/core/memory_track.h) | 0 | ||||
-rw-r--r-- | library/cpp/actors/util/memory_tracker.cpp (renamed from library/cpp/actors/core/memory_tracker.cpp) | 0 | ||||
-rw-r--r-- | library/cpp/actors/util/memory_tracker.h (renamed from library/cpp/actors/core/memory_tracker.h) | 0 | ||||
-rw-r--r-- | library/cpp/actors/util/memory_tracker_ut.cpp (renamed from library/cpp/actors/core/memory_tracker_ut.cpp) | 3 | ||||
-rw-r--r-- | library/cpp/actors/util/shared_data.cpp | 49 | ||||
-rw-r--r-- | library/cpp/actors/util/shared_data.h | 211 | ||||
-rw-r--r-- | library/cpp/actors/util/shared_data_rope_backend.h | 37 | ||||
-rw-r--r-- | library/cpp/actors/util/shared_data_rope_backend_ut.cpp | 231 | ||||
-rw-r--r-- | library/cpp/actors/util/shared_data_ut.cpp | 186 |
12 files changed, 719 insertions, 3 deletions
diff --git a/library/cpp/actors/core/CMakeLists.txt b/library/cpp/actors/core/CMakeLists.txt index 64c617307c..3dc01c29c1 100644 --- a/library/cpp/actors/core/CMakeLists.txt +++ b/library/cpp/actors/core/CMakeLists.txt @@ -47,8 +47,6 @@ target_sources(cpp-actors-core PRIVATE ${CMAKE_SOURCE_DIR}/library/cpp/actors/core/log.cpp ${CMAKE_SOURCE_DIR}/library/cpp/actors/core/log_settings.cpp ${CMAKE_SOURCE_DIR}/library/cpp/actors/core/mailbox.cpp - ${CMAKE_SOURCE_DIR}/library/cpp/actors/core/memory_track.cpp - ${CMAKE_SOURCE_DIR}/library/cpp/actors/core/memory_tracker.cpp ${CMAKE_SOURCE_DIR}/library/cpp/actors/core/monotonic.cpp ${CMAKE_SOURCE_DIR}/library/cpp/actors/core/monotonic_provider.cpp ${CMAKE_SOURCE_DIR}/library/cpp/actors/core/worker_context.cpp diff --git a/library/cpp/actors/util/CMakeLists.txt b/library/cpp/actors/util/CMakeLists.txt index 233e1fe0fc..19f683cb2a 100644 --- a/library/cpp/actors/util/CMakeLists.txt +++ b/library/cpp/actors/util/CMakeLists.txt @@ -16,6 +16,9 @@ target_link_libraries(cpp-actors-util PUBLIC ) target_sources(cpp-actors-util PRIVATE ${CMAKE_SOURCE_DIR}/library/cpp/actors/util/affinity.cpp + ${CMAKE_SOURCE_DIR}/library/cpp/actors/util/memory_track.cpp + ${CMAKE_SOURCE_DIR}/library/cpp/actors/util/memory_tracker.cpp + ${CMAKE_SOURCE_DIR}/library/cpp/actors/util/shared_data.cpp ${CMAKE_SOURCE_DIR}/library/cpp/actors/util/should_continue.cpp ${CMAKE_SOURCE_DIR}/library/cpp/actors/util/threadparkpad.cpp ) diff --git a/library/cpp/actors/core/memory_track.cpp b/library/cpp/actors/util/memory_track.cpp index 5f422116be..5f422116be 100644 --- a/library/cpp/actors/core/memory_track.cpp +++ b/library/cpp/actors/util/memory_track.cpp diff --git a/library/cpp/actors/core/memory_track.h b/library/cpp/actors/util/memory_track.h index 6035333eeb..6035333eeb 100644 --- a/library/cpp/actors/core/memory_track.h +++ b/library/cpp/actors/util/memory_track.h diff --git a/library/cpp/actors/core/memory_tracker.cpp b/library/cpp/actors/util/memory_tracker.cpp index 8a12452c71..8a12452c71 100644 --- a/library/cpp/actors/core/memory_tracker.cpp +++ b/library/cpp/actors/util/memory_tracker.cpp diff --git a/library/cpp/actors/core/memory_tracker.h b/library/cpp/actors/util/memory_tracker.h index e74508191b..e74508191b 100644 --- a/library/cpp/actors/core/memory_tracker.h +++ b/library/cpp/actors/util/memory_tracker.h diff --git a/library/cpp/actors/core/memory_tracker_ut.cpp b/library/cpp/actors/util/memory_tracker_ut.cpp index d168214da6..1b8eff7cc5 100644 --- a/library/cpp/actors/core/memory_tracker_ut.cpp +++ b/library/cpp/actors/util/memory_tracker_ut.cpp @@ -36,6 +36,7 @@ struct TNameLabeled char payload[32]; }; +#ifndef _win_ Y_UNIT_TEST(Gathering) { TMemoryTracker::Instance()->Initialize(); @@ -75,7 +76,7 @@ Y_UNIT_TEST(Gathering) UNIT_ASSERT(metrics[nameIndex].GetMemory() == 0); UNIT_ASSERT(metrics[nameIndex].GetCount() == 0); } - +#endif static constexpr char InContainerLabel[] = "InContainerLabel"; diff --git a/library/cpp/actors/util/shared_data.cpp b/library/cpp/actors/util/shared_data.cpp new file mode 100644 index 0000000000..209ca7bcbd --- /dev/null +++ b/library/cpp/actors/util/shared_data.cpp @@ -0,0 +1,49 @@ +#include "shared_data.h" + +#include "memory_tracker.h" + +#include <util/system/sys_alloc.h> +#include <util/system/sanitizers.h> + +namespace NActors { + + static constexpr char MemoryLabelSharedData[] = "Tablet/TSharedData/Buffers"; + + char* TSharedData::Allocate(size_t size) { + char* data = nullptr; + if (size > 0) { + if (size >= MaxDataSize) { + throw std::length_error("Allocate size overflow"); + } + auto allocSize = OverheadSize + size; + char* raw = reinterpret_cast<char*>(y_allocate(allocSize)); + + auto* privateHeader = reinterpret_cast<TPrivateHeader*>(raw); + privateHeader->AllocSize = allocSize; + NActors::NMemory::TLabel<MemoryLabelSharedData>::Add(allocSize); + + auto* header = reinterpret_cast<THeader*>(raw + PrivateHeaderSize); + header->RefCount = 1; + header->Owner = nullptr; + + data = raw + OverheadSize; + NSan::Poison(data, size); + } + return data; + } + + void TSharedData::Deallocate(char* data) noexcept { + if (data) { + char* raw = data - OverheadSize; + + auto* privateHeader = reinterpret_cast<TPrivateHeader*>(raw); + NActors::NMemory::TLabel<MemoryLabelSharedData>::Sub(privateHeader->AllocSize); + + auto* header = reinterpret_cast<THeader*>(raw + PrivateHeaderSize); + Y_VERIFY_DEBUG(header->Owner == nullptr); + + y_deallocate(raw); + } + } + +} diff --git a/library/cpp/actors/util/shared_data.h b/library/cpp/actors/util/shared_data.h new file mode 100644 index 0000000000..6fdc9bf35f --- /dev/null +++ b/library/cpp/actors/util/shared_data.h @@ -0,0 +1,211 @@ +#pragma once + +#include <library/cpp/deprecated/atomic/atomic.h> + +#include <util/system/types.h> +#include <util/system/compiler.h> +#include <util/generic/array_ref.h> + +namespace NActors { + + class TSharedData { + public: + class IOwner { + public: + virtual ~IOwner() = default; + + virtual void Deallocate(char*) noexcept = 0; + }; + + struct TPrivateHeader { + size_t AllocSize; + size_t Pad; + }; + + static_assert(sizeof(TPrivateHeader) == 16, "TPrivateHeader has an unexpected size"); + + struct THeader { + TAtomic RefCount; + IOwner* Owner; + }; + + static_assert(sizeof(THeader) == 16, "THeader has an unexpected size"); + + enum : size_t { + PrivateHeaderSize = sizeof(TPrivateHeader), + HeaderSize = sizeof(THeader), + OverheadSize = PrivateHeaderSize + HeaderSize, + MaxDataSize = (std::numeric_limits<size_t>::max() - OverheadSize) + }; + + public: + TSharedData() noexcept + : Data_(nullptr) + , Size_(0) + { } + + ~TSharedData() noexcept { + Release(); + } + + TSharedData(const TSharedData& other) noexcept + : Data_(other.Data_) + , Size_(other.Size_) + { + AddRef(); + } + + TSharedData(TSharedData&& other) noexcept + : Data_(other.Data_) + , Size_(other.Size_) + { + other.Data_ = nullptr; + other.Size_ = 0; + } + + TSharedData& operator=(const TSharedData& other) noexcept { + if (this != &other) { + Release(); + Data_ = other.Data_; + Size_ = other.Size_; + AddRef(); + } + return *this; + } + + TSharedData& operator=(TSharedData&& other) noexcept { + if (this != &other) { + Release(); + Data_ = other.Data_; + Size_ = other.Size_; + other.Data_ = nullptr; + other.Size_ = 0; + } + return *this; + } + + Y_FORCE_INLINE explicit operator bool() const { return Size_ > 0; } + + Y_FORCE_INLINE char* mutable_data() { Y_VERIFY_DEBUG(IsPrivate()); return Data_; } + Y_FORCE_INLINE char* mutable_begin() { Y_VERIFY_DEBUG(IsPrivate()); return Data_; } + Y_FORCE_INLINE char* mutable_end() { Y_VERIFY_DEBUG(IsPrivate()); return Data_ + Size_; } + + Y_FORCE_INLINE const char* data() const { return Data_; } + Y_FORCE_INLINE const char* begin() const { return Data_; } + Y_FORCE_INLINE const char* end() const { return Data_ + Size_; } + + Y_FORCE_INLINE size_t size() const { return Size_; } + + /** + * Trims data to the specified size + * Underlying data is not reallocated + * Returns trimmed amount in bytes + */ + size_t Trim(size_t size) noexcept { + size_t trimmed = 0; + if (Size_ > size) { + trimmed = Size_ - size; + if (!size) { + Release(); + Data_ = nullptr; + } + Size_ = size; + } + return trimmed; + } + + /** + * Returns a view of underlying data starting with pos and up to len bytes + */ + TStringBuf Slice(size_t pos = 0, size_t len = -1) const noexcept { + pos = Min(pos, Size_); + len = Min(len, Size_ - pos); + return { Data_ + pos, len }; + } + + bool IsPrivate() const { + return Data_ ? IsPrivate(Header()) : true; + } + + bool IsShared() const { + return !IsPrivate(); + } + + TString ToString() const { + return TString(data(), size()); + } + + /** + * Attach to pre-allocated data with a preceding THeader + */ + static TSharedData AttachUnsafe(char* data, size_t size) noexcept { + TSharedData result; + result.Data_ = data; + result.Size_ = size; + return result; + } + + /** + * Make uninitialized buffer of the specified size + */ + static TSharedData Uninitialized(size_t size) { + return AttachUnsafe(Allocate(size), size); + } + + /** + * Make a copy of the specified data + */ + static TSharedData Copy(const void* data, size_t size) { + TSharedData result = Uninitialized(size); + if (size) { + ::memcpy(result.Data_, data, size); + } + return result; + } + + /** + * Make a copy of the specified data + */ + static TSharedData Copy(TArrayRef<const char> data) { + return Copy(data.data(), data.size()); + } + + private: + Y_FORCE_INLINE THeader* Header() const noexcept { + Y_VERIFY_DEBUG(Data_); + return reinterpret_cast<THeader*>(Data_ - sizeof(THeader)); + } + + static bool IsPrivate(THeader* header) noexcept { + return 1 == AtomicGet(header->RefCount); + } + + void AddRef() noexcept { + if (Data_) { + AtomicIncrement(Header()->RefCount); + } + } + + void Release() noexcept { + if (Data_) { + auto* header = Header(); + if (IsPrivate(header) || 0 == AtomicDecrement(header->RefCount)) { + if (auto* owner = header->Owner) { + owner->Deallocate(Data_); + } else { + Deallocate(Data_); + } + } + } + } + + private: + static char* Allocate(size_t size); + static void Deallocate(char* data) noexcept; + + private: + char* Data_; + size_t Size_; + }; + +} diff --git a/library/cpp/actors/util/shared_data_rope_backend.h b/library/cpp/actors/util/shared_data_rope_backend.h new file mode 100644 index 0000000000..3add6afd27 --- /dev/null +++ b/library/cpp/actors/util/shared_data_rope_backend.h @@ -0,0 +1,37 @@ +#pragma once + +#include <library/cpp/actors/util/rope.h> + +#include "shared_data.h" + +namespace NActors { + +class TRopeSharedDataBackend : public IRopeChunkBackend { + TSharedData Buffer; + +public: + TRopeSharedDataBackend(TSharedData buffer) + : Buffer(std::move(buffer)) + {} + + TData GetData() const override { + return {Buffer.data(), Buffer.size()}; + } + + TMutData GetDataMut() override { + if(Buffer.IsShared()) { + Buffer = TSharedData::Copy(Buffer.data(), Buffer.size()); + } + return {Buffer.mutable_data(), Buffer.size()}; + } + + TMutData UnsafeGetDataMut() override { + return {const_cast<char *>(Buffer.data()), Buffer.size()}; + } + + size_t GetCapacity() const override { + return Buffer.size(); + } +}; + +} // namespace NActors diff --git a/library/cpp/actors/util/shared_data_rope_backend_ut.cpp b/library/cpp/actors/util/shared_data_rope_backend_ut.cpp new file mode 100644 index 0000000000..3ffe14a544 --- /dev/null +++ b/library/cpp/actors/util/shared_data_rope_backend_ut.cpp @@ -0,0 +1,231 @@ +#include <library/cpp/actors/util/rope.h> +#include <library/cpp/testing/unittest/registar.h> +#include <util/random/random.h> + +#include "shared_data_rope_backend.h" + +namespace NActors { + + namespace { + + TRope CreateRope(TString s, size_t sliceSize) { + TRope res; + for (size_t i = 0; i < s.size(); ) { + size_t len = std::min(sliceSize, s.size() - i); + if (i % 2) { + auto str = s.substr(i, len); + res.Insert(res.End(), TRope(MakeIntrusive<TRopeSharedDataBackend>( + TSharedData::Copy(str.data(), str.size())))); + } else { + res.Insert(res.End(), TRope(s.substr(i, len))); + } + i += len; + } + return res; + } + + TString RopeToString(const TRope& rope) { + TString res; + auto iter = rope.Begin(); + while (iter != rope.End()) { + res.append(iter.ContiguousData(), iter.ContiguousSize()); + iter.AdvanceToNextContiguousBlock(); + } + + UNIT_ASSERT_VALUES_EQUAL(rope.GetSize(), res.size()); + + TString temp = TString::Uninitialized(rope.GetSize()); + rope.Begin().ExtractPlainDataAndAdvance(temp.Detach(), temp.size()); + UNIT_ASSERT_VALUES_EQUAL(temp, res); + + return res; + } + + TString Text = "No elements are copied or moved, only the internal pointers of the list nodes are re-pointed."; + + } + + Y_UNIT_TEST_SUITE(TRopeSharedDataBackend) { + + // Same tests as in TRope but with new CreateRope using TSharedData backend + + Y_UNIT_TEST(Leak) { + const size_t begin = 10, end = 20; + TRope rope = CreateRope(Text, 10); + rope.Erase(rope.Begin() + begin, rope.Begin() + end); + } + + Y_UNIT_TEST(BasicRange) { + TRope rope = CreateRope(Text, 10); + for (size_t begin = 0; begin < Text.size(); ++begin) { + for (size_t end = begin; end <= Text.size(); ++end) { + TRope::TIterator rBegin = rope.Begin() + begin; + TRope::TIterator rEnd = rope.Begin() + end; + UNIT_ASSERT_VALUES_EQUAL(RopeToString(TRope(rBegin, rEnd)), Text.substr(begin, end - begin)); + } + } + } + + Y_UNIT_TEST(Erase) { + for (size_t begin = 0; begin < Text.size(); ++begin) { + for (size_t end = begin; end <= Text.size(); ++end) { + TRope rope = CreateRope(Text, 10); + rope.Erase(rope.Begin() + begin, rope.Begin() + end); + TString text = Text; + text.erase(text.begin() + begin, text.begin() + end); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(rope), text); + } + } + } + + Y_UNIT_TEST(Insert) { + TRope rope = CreateRope(Text, 10); + for (size_t begin = 0; begin < Text.size(); ++begin) { + for (size_t end = begin; end <= Text.size(); ++end) { + TRope part = TRope(rope.Begin() + begin, rope.Begin() + end); + for (size_t where = 0; where <= Text.size(); ++where) { + TRope x(rope); + x.Insert(x.Begin() + where, TRope(part)); + UNIT_ASSERT_VALUES_EQUAL(x.GetSize(), rope.GetSize() + part.GetSize()); + TString text = Text; + text.insert(text.begin() + where, Text.begin() + begin, Text.begin() + end); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(x), text); + } + } + } + } + + Y_UNIT_TEST(Extract) { + for (size_t begin = 0; begin < Text.size(); ++begin) { + for (size_t end = begin; end <= Text.size(); ++end) { + TRope rope = CreateRope(Text, 10); + TRope part = rope.Extract(rope.Begin() + begin, rope.Begin() + end); + TString text = Text; + text.erase(text.begin() + begin, text.begin() + end); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(rope), text); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(part), Text.substr(begin, end - begin)); + } + } + } + + Y_UNIT_TEST(EraseFront) { + for (size_t pos = 0; pos <= Text.size(); ++pos) { + TRope rope = CreateRope(Text, 10); + rope.EraseFront(pos); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(rope), Text.substr(pos)); + } + } + + Y_UNIT_TEST(EraseBack) { + for (size_t pos = 0; pos <= Text.size(); ++pos) { + TRope rope = CreateRope(Text, 10); + rope.EraseBack(pos); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(rope), Text.substr(0, Text.size() - pos)); + } + } + + Y_UNIT_TEST(ExtractFront) { + for (size_t step = 1; step <= Text.size(); ++step) { + TRope rope = CreateRope(Text, 10); + TRope out; + while (const size_t len = Min(step, rope.GetSize())) { + rope.ExtractFront(len, &out); + UNIT_ASSERT(rope.GetSize() + out.GetSize() == Text.size()); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(out), Text.substr(0, out.GetSize())); + } + } + } + + Y_UNIT_TEST(ExtractFrontPlain) { + for (size_t step = 1; step <= Text.size(); ++step) { + TRope rope = CreateRope(Text, 10); + TString buffer = Text; + auto it = rope.Begin(); + size_t remain = rope.GetSize(); + while (const size_t len = Min(step, remain)) { + TString data = TString::Uninitialized(len); + it.ExtractPlainDataAndAdvance(data.Detach(), data.size()); + UNIT_ASSERT_VALUES_EQUAL(data, buffer.substr(0, len)); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(TRope(it, rope.End())), buffer.substr(len)); + buffer = buffer.substr(len); + remain -= len; + } + } + } + + Y_UNIT_TEST(Glueing) { + TRope rope = CreateRope(Text, 10); + for (size_t begin = 0; begin <= Text.size(); ++begin) { + for (size_t end = begin; end <= Text.size(); ++end) { + TString repr = rope.DebugString(); + TRope temp = rope.Extract(rope.Position(begin), rope.Position(end)); + rope.Insert(rope.Position(begin), std::move(temp)); + UNIT_ASSERT_VALUES_EQUAL(repr, rope.DebugString()); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(rope), Text); + } + } + } + + Y_UNIT_TEST(IterWalk) { + TRope rope = CreateRope(Text, 10); + for (size_t step1 = 0; step1 <= rope.GetSize(); ++step1) { + for (size_t step2 = 0; step2 <= step1; ++step2) { + TRope::TConstIterator iter = rope.Begin(); + iter += step1; + iter -= step2; + UNIT_ASSERT(iter == rope.Position(step1 - step2)); + } + } + } + + Y_UNIT_TEST(Compare) { + auto check = [](const TString& x, const TString& y) { + const TRope xRope = CreateRope(x, 7); + const TRope yRope = CreateRope(y, 11); + UNIT_ASSERT_VALUES_EQUAL(xRope == yRope, x == y); + UNIT_ASSERT_VALUES_EQUAL(xRope != yRope, x != y); + UNIT_ASSERT_VALUES_EQUAL(xRope < yRope, x < y); + UNIT_ASSERT_VALUES_EQUAL(xRope <= yRope, x <= y); + UNIT_ASSERT_VALUES_EQUAL(xRope > yRope, x > y); + UNIT_ASSERT_VALUES_EQUAL(xRope >= yRope, x >= y); + }; + + TVector<TString> pool; + for (size_t k = 0; k < 10; ++k) { + size_t len = RandomNumber<size_t>(100) + 100; + TString s = TString::Uninitialized(len); + char *p = s.Detach(); + for (size_t j = 0; j < len; ++j) { + *p++ = RandomNumber<unsigned char>(); + } + pool.push_back(std::move(s)); + } + + for (const TString& x : pool) { + for (const TString& y : pool) { + check(x, y); + } + } + } + + // Specific TSharedDataRopeBackend tests + + Y_UNIT_TEST(RopeOnlyBorrows) { + TSharedData data = TSharedData::Copy(Text.data(), Text.size()); + { + TRope rope; + rope.Insert(rope.End(), TRope(MakeIntrusive<TRopeSharedDataBackend>(data))); + UNIT_ASSERT(data.IsShared()); + TSharedData dataCopy = data; + UNIT_ASSERT(dataCopy.IsShared()); + UNIT_ASSERT_EQUAL(dataCopy.data(), data.data()); + rope.Insert(rope.End(), TRope(MakeIntrusive<TRopeSharedDataBackend>(data))); + rope.Insert(rope.End(), TRope(MakeIntrusive<TRopeSharedDataBackend>(data))); + dataCopy.Trim(10); + UNIT_ASSERT_EQUAL(rope.GetSize(), data.size() * 3); + } + UNIT_ASSERT(data.IsPrivate()); + } + } + +} // namespace NActors diff --git a/library/cpp/actors/util/shared_data_ut.cpp b/library/cpp/actors/util/shared_data_ut.cpp new file mode 100644 index 0000000000..8ed0df6866 --- /dev/null +++ b/library/cpp/actors/util/shared_data_ut.cpp @@ -0,0 +1,186 @@ +#include "shared_data.h" + +#include <library/cpp/testing/unittest/registar.h> + +#include <util/generic/hash.h> +#include <util/generic/deque.h> +#include <util/system/sys_alloc.h> + +namespace NActors { + + Y_UNIT_TEST_SUITE(TSharedDataTest) { + + Y_UNIT_TEST(BasicBehavior) { + auto data = TSharedData::Copy("Hello", 5); + UNIT_ASSERT(data.IsPrivate()); + UNIT_ASSERT(!data.IsShared()); + UNIT_ASSERT_VALUES_EQUAL(data.size(), 5u); + UNIT_ASSERT_VALUES_EQUAL(data.end() - data.begin(), 5u); + UNIT_ASSERT_VALUES_EQUAL(data.mutable_end() - data.mutable_begin(), 5u); + UNIT_ASSERT(data.begin() == data.data()); + UNIT_ASSERT(data.mutable_data() == data.data()); + UNIT_ASSERT(data.mutable_begin() == data.mutable_data()); + + UNIT_ASSERT_VALUES_EQUAL(data.ToString(), TString("Hello")); + UNIT_ASSERT_VALUES_EQUAL(::memcmp(data.data(), "Hello", 5), 0); + + auto link = data; + UNIT_ASSERT(!link.IsPrivate()); + UNIT_ASSERT(!data.IsPrivate()); + UNIT_ASSERT(link.IsShared()); + UNIT_ASSERT(data.IsShared()); + UNIT_ASSERT(link.data() == data.data()); + UNIT_ASSERT(link.size() == data.size()); + + link = { }; + UNIT_ASSERT(link.IsPrivate()); + UNIT_ASSERT(data.IsPrivate()); + UNIT_ASSERT(!link.IsShared()); + UNIT_ASSERT(!data.IsShared()); + + UNIT_ASSERT_VALUES_EQUAL(TString(data.Slice()), TString("Hello")); + UNIT_ASSERT_VALUES_EQUAL(TString(data.Slice(1)), TString("ello")); + UNIT_ASSERT_VALUES_EQUAL(TString(data.Slice(1, 3)), TString("ell")); + UNIT_ASSERT_VALUES_EQUAL(TString(data.Slice(1, 100)), TString("ello")); + UNIT_ASSERT_VALUES_EQUAL(TString(data.Slice(0, 4)), TString("Hell")); + } + + Y_UNIT_TEST(TrimBehavior) { + auto data = TSharedData::Uninitialized(42); + + UNIT_ASSERT_VALUES_EQUAL(data.size(), 42u); + UNIT_ASSERT(data.data() != nullptr); + + // Trim to non-zero does not change addresses + const char* ptr1 = data.data(); + data.Trim(31); + const char* ptr2 = data.data(); + + UNIT_ASSERT_VALUES_EQUAL(data.size(), 31u); + UNIT_ASSERT(ptr1 == ptr2); + + // Trim to zero releases underlying data + data.Trim(0); + + UNIT_ASSERT_VALUES_EQUAL(data.size(), 0u); + UNIT_ASSERT(data.data() == nullptr); + } + + class TCustomOwner : public TSharedData::IOwner { + using THeader = TSharedData::THeader; + + public: + TSharedData Allocate(size_t size) { + char* raw = reinterpret_cast<char*>(y_allocate(sizeof(THeader) + size)); + THeader* header = reinterpret_cast<THeader*>(raw); + header->RefCount = 1; + header->Owner = this; + char* data = raw + sizeof(THeader); + Y_VERIFY(Allocated_.insert(data).second); + return TSharedData::AttachUnsafe(data, size); + } + + void Deallocate(char* data) noexcept { + Y_VERIFY(Allocated_.erase(data) > 0); + char* raw = data - sizeof(THeader); + y_deallocate(raw); + Deallocated_.push_back(data); + } + + char* NextDeallocated() { + char* result = nullptr; + if (Deallocated_) { + result = Deallocated_.front(); + Deallocated_.pop_front(); + } + return result; + } + + private: + THashSet<void*> Allocated_; + TDeque<char*> Deallocated_; + }; + + Y_UNIT_TEST(CustomOwner) { + TCustomOwner owner; + const char* ptr; + + // Test destructor releases data + { + auto data = owner.Allocate(42); + UNIT_ASSERT_VALUES_EQUAL(data.size(), 42u); + ptr = data.data(); + UNIT_ASSERT(owner.NextDeallocated() == nullptr); + } + + UNIT_ASSERT(owner.NextDeallocated() == ptr); + UNIT_ASSERT(owner.NextDeallocated() == nullptr); + + // Test assignment releases data + { + auto data = owner.Allocate(42); + UNIT_ASSERT_VALUES_EQUAL(data.size(), 42u); + ptr = data.data(); + UNIT_ASSERT(owner.NextDeallocated() == nullptr); + data = { }; + } + + UNIT_ASSERT(owner.NextDeallocated() == ptr); + UNIT_ASSERT(owner.NextDeallocated() == nullptr); + + // Test copies keep references correctly + { + auto data = owner.Allocate(42); + UNIT_ASSERT_VALUES_EQUAL(data.size(), 42u); + ptr = data.data(); + auto copy = data; + UNIT_ASSERT_VALUES_EQUAL(copy.size(), 42u); + UNIT_ASSERT(copy.data() == ptr); + data = { }; + UNIT_ASSERT_VALUES_EQUAL(data.size(), 0u); + UNIT_ASSERT(data.data() == nullptr); + UNIT_ASSERT(owner.NextDeallocated() == nullptr); + } + + UNIT_ASSERT(owner.NextDeallocated() == ptr); + UNIT_ASSERT(owner.NextDeallocated() == nullptr); + + // Test assignment releases correct data + { + auto data1 = owner.Allocate(42); + UNIT_ASSERT_VALUES_EQUAL(data1.size(), 42u); + auto data2 = owner.Allocate(31); + UNIT_ASSERT_VALUES_EQUAL(data2.size(), 31u); + ptr = data1.data(); + UNIT_ASSERT(owner.NextDeallocated() == nullptr); + data1 = data2; + UNIT_ASSERT(owner.NextDeallocated() == ptr); + UNIT_ASSERT(owner.NextDeallocated() == nullptr); + ptr = data2.data(); + UNIT_ASSERT_VALUES_EQUAL(data1.size(), 31u); + UNIT_ASSERT(data1.data() == ptr); + } + + UNIT_ASSERT(owner.NextDeallocated() == ptr); + UNIT_ASSERT(owner.NextDeallocated() == nullptr); + + // Test moves don't produce dangling references + { + auto data = owner.Allocate(42); + UNIT_ASSERT_VALUES_EQUAL(data.size(), 42u); + ptr = data.data(); + auto moved = std::move(data); + UNIT_ASSERT_VALUES_EQUAL(moved.size(), 42u); + UNIT_ASSERT(moved.data() == ptr); + UNIT_ASSERT_VALUES_EQUAL(data.size(), 0u); + UNIT_ASSERT(data.data() == nullptr); + UNIT_ASSERT(owner.NextDeallocated() == nullptr); + } + + UNIT_ASSERT(owner.NextDeallocated() == ptr); + UNIT_ASSERT(owner.NextDeallocated() == nullptr); + } + + } + +} |