aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp
diff options
context:
space:
mode:
authorinnokentii <innokentii@yandex-team.com>2022-09-09 13:56:14 +0300
committerinnokentii <innokentii@yandex-team.com>2022-09-09 13:56:14 +0300
commit7cd934b41bf8f1487cbb4eb783a1e7e7e0a8cbdc (patch)
tree685d3025cdc4b14befe8b9fd9c1448adadd321f9 /library/cpp
parent695f38b2d33b4c1962bc3ae11737025406116832 (diff)
downloadydb-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.txt2
-rw-r--r--library/cpp/actors/util/CMakeLists.txt3
-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.cpp49
-rw-r--r--library/cpp/actors/util/shared_data.h211
-rw-r--r--library/cpp/actors/util/shared_data_rope_backend.h37
-rw-r--r--library/cpp/actors/util/shared_data_rope_backend_ut.cpp231
-rw-r--r--library/cpp/actors/util/shared_data_ut.cpp186
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);
+ }
+
+ }
+
+}