summaryrefslogtreecommitdiffstats
path: root/library/cpp
diff options
context:
space:
mode:
Diffstat (limited to 'library/cpp')
-rw-r--r--library/cpp/yt/containers/default_map-inl.h58
-rw-r--r--library/cpp/yt/containers/default_map.h64
-rw-r--r--library/cpp/yt/containers/ring_queue.h382
-rw-r--r--library/cpp/yt/containers/static_ring_queue-inl.h69
-rw-r--r--library/cpp/yt/containers/static_ring_queue.h29
-rw-r--r--library/cpp/yt/containers/unittests/default_map_ut.cpp27
-rw-r--r--library/cpp/yt/containers/unittests/ring_queue_ut.cpp134
-rw-r--r--library/cpp/yt/containers/unittests/static_ring_queue_ut.cpp130
-rw-r--r--library/cpp/yt/containers/unittests/ya.make4
-rw-r--r--library/cpp/yt/containers/ya.make1
-rw-r--r--library/cpp/yt/threading/copyable_atomic.h47
-rw-r--r--library/cpp/yt/threading/unittests/copyable_atomic_ut.cpp86
-rw-r--r--library/cpp/yt/threading/unittests/ya.make1
13 files changed, 1032 insertions, 0 deletions
diff --git a/library/cpp/yt/containers/default_map-inl.h b/library/cpp/yt/containers/default_map-inl.h
new file mode 100644
index 00000000000..95e29623e0e
--- /dev/null
+++ b/library/cpp/yt/containers/default_map-inl.h
@@ -0,0 +1,58 @@
+#ifndef DEFAULT_MAP_INL_H_
+#error "Direct inclusion of this file is not allowed, include default_map.h"
+// For the sake of sane code completion.
+#include "default_map.h"
+#endif
+
+#include <utility>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+TDefaultMap<T>::TDefaultMap(mapped_type defaultValue)
+ : DefaultValue_(std::move(defaultValue))
+{ }
+
+template <class T>
+template <class K>
+typename TDefaultMap<T>::mapped_type& TDefaultMap<T>::operator[](const K& key)
+{
+ if (auto it = T::find(key); it != T::end()) {
+ return it->second;
+ }
+ return T::insert({key, DefaultValue_}).first->second;
+}
+
+template <class T>
+template <class K>
+const typename TDefaultMap<T>::mapped_type& TDefaultMap<T>::GetOrDefault(const K& key) const
+{
+ auto it = T::find(key);
+ return it == T::end() ? DefaultValue_ : it->second;
+}
+
+template <class T>
+T& TDefaultMap<T>::AsUnderlying() noexcept
+{
+ return static_cast<T&>(*this);
+}
+
+template <class T>
+const T& TDefaultMap<T>::AsUnderlying() const noexcept
+{
+ return static_cast<const T&>(*this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+void FormatValue(TStringBuilderBase* builder, const TDefaultMap<T>& map, TStringBuf format)
+{
+ FormatValue(builder, static_cast<const T&>(map), format);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/containers/default_map.h b/library/cpp/yt/containers/default_map.h
new file mode 100644
index 00000000000..f40551a9dd3
--- /dev/null
+++ b/library/cpp/yt/containers/default_map.h
@@ -0,0 +1,64 @@
+#pragma once
+
+#include <library/cpp/yt/misc/property.h>
+
+#include <library/cpp/yt/string/string_builder.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+class TDefaultMap
+ : public T
+{
+public:
+ using TUnderlying = T;
+ using mapped_type = typename T::mapped_type;
+
+ DEFINE_BYREF_RO_PROPERTY(mapped_type, DefaultValue);
+
+ TDefaultMap() = default;
+
+ explicit TDefaultMap(mapped_type defaultValue);
+
+ template <class K>
+ mapped_type& operator[](const K& key);
+
+ template <class K>
+ const mapped_type& GetOrDefault(const K& key) const;
+
+ T& AsUnderlying() noexcept;
+ const T& AsUnderlying() const noexcept;
+
+ // NB: There are no Save()/Load() since it can be easiliy (de)serialized
+ // manually. Moreover, it's easy to forget write compats when replacing
+ // THashTable to this one since Save()/Load() works out of the box.
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+void FormatValue(TStringBuilderBase* builder, const TDefaultMap<T>& map, TStringBuf format);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+struct TIsDefaultMap
+{
+ static constexpr bool Value = false;
+};
+
+template <class T>
+struct TIsDefaultMap<TDefaultMap<T>>
+{
+ static constexpr bool Value = true;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+#define DEFAULT_MAP_INL_H_
+#include "default_map-inl.h"
+#undef DEFAULT_MAP_INL_H_
diff --git a/library/cpp/yt/containers/ring_queue.h b/library/cpp/yt/containers/ring_queue.h
new file mode 100644
index 00000000000..c3be1fa5844
--- /dev/null
+++ b/library/cpp/yt/containers/ring_queue.h
@@ -0,0 +1,382 @@
+#pragma once
+
+#include <library/cpp/yt/assert/assert.h>
+
+#include <library/cpp/yt/string/format.h>
+
+#include <cstring>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A simple and high-performant drop-in replacement for |std::queue|.
+/*!
+ * Things to keep in mind:
+ * - Capacity is doubled each time it is exhausted and is never shrunk back.
+ * - Iteration is supported but iterator movements involve calling |move_forward| and |move_backward|.
+ * - |T| must be nothrow move constructable.
+ */
+template <class T, class TAllocator = std::allocator<T>>
+class TRingQueue
+{
+public:
+ using value_type = T;
+ using reference = T&;
+ using const_reference = const T&;
+ using pointer = T*;
+ using const_pointer = const T*;
+ using size_type = size_t;
+
+ static_assert(std::is_nothrow_move_constructible<T>::value, "T must be nothrow move constructable.");
+
+ class TIterator
+ {
+ public:
+ TIterator() = default;
+ TIterator(const TIterator&) = default;
+
+ const T& operator*() const
+ {
+ return *Ptr_;
+ }
+
+ T& operator*()
+ {
+ return *Ptr_;
+ }
+
+ const T* operator->() const
+ {
+ return Ptr_;
+ }
+
+ T* operator->()
+ {
+ return Ptr_;
+ }
+
+ bool operator==(TIterator other) const
+ {
+ return Ptr_ == other.Ptr_;
+ }
+
+ TIterator& operator=(TIterator other)
+ {
+ Ptr_ = other.Ptr_;
+ return *this;
+ }
+
+ private:
+ friend class TRingQueue<T, TAllocator>;
+
+ explicit TIterator(T* ptr)
+ : Ptr_(ptr)
+ { }
+
+ T* Ptr_;
+ };
+
+ TRingQueue()
+ : TRingQueue(TAllocator())
+ { }
+
+ explicit TRingQueue(const TAllocator& allocator)
+ : Allocator_(allocator)
+ {
+ Capacity_ = InitialCapacity;
+ Begin_ = Allocator_.allocate(Capacity_);
+ End_ = Begin_ + Capacity_;
+
+ Size_ = 0;
+ Head_ = Tail_ = Begin_;
+ }
+
+ TRingQueue(TRingQueue&& other) noexcept
+ : Allocator_(std::move(other.Allocator_))
+ {
+ Capacity_ = other.Capacity_;
+ Begin_ = other.Begin_;
+ End_ = other.End_;
+
+ Size_ = other.Size_;
+ Head_ = other.Head_;
+ Tail_ = other.Tail_;
+
+ other.Capacity_ = other.Size_ = 0;
+ other.Begin_ = other.End_ = other.Head_ = other.Tail_ = nullptr;
+ }
+
+ TRingQueue(const TRingQueue& other) = delete;
+
+ ~TRingQueue()
+ {
+ DestroyElements();
+ if (Begin_) {
+ Allocator_.deallocate(Begin_, Capacity_);
+ }
+ }
+
+ T& front()
+ {
+ return *Head_;
+ }
+
+ const T& front() const
+ {
+ return *Head_;
+ }
+
+ T& back()
+ {
+ if (Tail_ == Begin_) {
+ return *(End_ - 1);
+ } else {
+ return *(Tail_ - 1);
+ }
+ }
+
+ const T& back() const
+ {
+ if (Tail_ == Begin_) {
+ return *(End_ - 1);
+ } else {
+ return *(Tail_ - 1);
+ }
+ }
+
+ // For performance reasons iterators do not provide their own operator++ and operator--.
+ // move_forward and move_backward are provided instead.
+ TIterator begin()
+ {
+ return TIterator(Head_);
+ }
+
+ TIterator end()
+ {
+ return TIterator(Tail_);
+ }
+
+ void move_forward(TIterator& it) const
+ {
+ ++it.Ptr_;
+ if (it.Ptr_ == End_) {
+ it.Ptr_ = Begin_;
+ }
+ }
+
+ void move_backward(TIterator& it) const
+ {
+ if (it.Ptr_ == Begin_) {
+ it.Ptr_ = End_ - 1;
+ } else {
+ --it.Ptr_;
+ }
+ }
+
+ T& operator[](size_t index)
+ {
+ YT_ASSERT(index < size());
+ auto headToEnd = static_cast<size_t>(End_ - Head_);
+ return index < headToEnd
+ ? Head_[index]
+ : Begin_[index - headToEnd];
+ }
+
+ const T& operator[](size_t index) const
+ {
+ return const_cast<TRingQueue*>(this)->operator[](index);
+ }
+
+ size_t size() const
+ {
+ return Size_;
+ }
+
+ bool empty() const
+ {
+ return Size_ == 0;
+ }
+
+ void push(const T& value)
+ {
+ BeforePush();
+ new(Tail_) T(value);
+ AfterPush();
+ }
+
+ void push(T&& value)
+ {
+ BeforePush();
+ new(Tail_) T(std::move(value));
+ AfterPush();
+ }
+
+ template <class... TArgs>
+ T& emplace(TArgs&&... args)
+ {
+ BeforePush();
+ auto* ptr = Tail_;
+ new (ptr) T(std::forward<TArgs>(args)...);
+ AfterPush();
+ return *ptr;
+ }
+
+ void pop()
+ {
+ YT_ASSERT(Size_ > 0);
+ Head_->T::~T();
+ ++Head_;
+ if (Head_ == End_) {
+ Head_ = Begin_;
+ }
+ --Size_;
+ }
+
+ void clear()
+ {
+ DestroyElements();
+ Size_ = 0;
+ Head_ = Tail_ = Begin_;
+ }
+
+private:
+ TAllocator Allocator_;
+
+ size_t Capacity_;
+ T* Begin_;
+ T* End_;
+
+ size_t Size_;
+ T* Head_;
+ T* Tail_;
+
+ static const size_t InitialCapacity = 16;
+
+ void DestroyElements()
+ {
+ if (Head_ <= Tail_) {
+ DestroyRange(Head_, Tail_);
+ } else {
+ DestroyRange(Head_, End_);
+ DestroyRange(Begin_, Tail_);
+ }
+ }
+
+ static void DestroyRange(T* begin, T* end)
+ {
+ if (!std::is_trivially_destructible<T>::value) {
+ for (auto* current = begin; current != end; ++current) {
+ current->T::~T();
+ }
+ }
+ }
+
+ static void MoveRange(T* begin, T* end, T* result)
+ {
+ if (std::is_trivially_move_constructible<T>::value) {
+ ::memcpy(result, begin, sizeof(T) * (end - begin));
+ } else {
+ for (auto* current = begin; current != end; ++current) {
+ new(result++) T(std::move(*current));
+ current->T::~T();
+ }
+ }
+ }
+
+ void BeforePush() noexcept
+ {
+ // NB: Avoid filling Items_ completely and collapsing Head_ with Tail_.
+ if (Size_ == Capacity_ - 1) {
+ auto newCapacity = Capacity_ * 2;
+ auto* newBegin = Allocator_.allocate(newCapacity);
+
+ if (Head_ <= Tail_) {
+ MoveRange(Head_, Tail_, newBegin);
+ } else {
+ MoveRange(Head_, End_, newBegin);
+ MoveRange(Begin_, Tail_, newBegin + (End_ - Head_));
+ }
+
+ Allocator_.deallocate(Begin_, Capacity_);
+
+ Capacity_ = newCapacity;
+ Begin_ = newBegin;
+ End_ = Begin_ + newCapacity;
+
+ Head_ = Begin_;
+ Tail_ = Head_ + Size_;
+ }
+ }
+
+ void AfterPush()
+ {
+ if (++Tail_ == End_) {
+ Tail_ = Begin_;
+ }
+ ++Size_;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T, class TAllocator = std::allocator<T>>
+class TRingQueueIterableWrapper
+{
+public:
+ using TContainer = TRingQueue<T, TAllocator>;
+ using TBaseIterator = typename TContainer::TIterator;
+
+ class TIterator
+ : public TBaseIterator
+ {
+ public:
+ TIterator(TBaseIterator baseIterator, TContainer& container)
+ : TBaseIterator(std::move(baseIterator))
+ , Container_(container)
+ { }
+
+ void operator++()
+ {
+ Container_.move_forward(*this);
+ }
+
+ private:
+ TRingQueue<T, TAllocator>& Container_;
+ };
+
+ TIterator begin() const
+ {
+ auto& container = const_cast<TContainer&>(Container_);
+ return TIterator(container.begin(), container);
+ }
+
+ TIterator end() const
+ {
+ auto& container = const_cast<TContainer&>(Container_);
+ return TIterator(container.end(), container);
+ }
+
+ TRingQueueIterableWrapper(TContainer& container)
+ : Container_(container)
+ { }
+
+ using value_type = typename TContainer::value_type;
+
+private:
+ TContainer& Container_;
+};
+
+namespace NDetail {
+
+template <class T, class Allocator>
+constexpr bool CKnownRange<TRingQueueIterableWrapper<T, Allocator>> = true;
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/containers/static_ring_queue-inl.h b/library/cpp/yt/containers/static_ring_queue-inl.h
new file mode 100644
index 00000000000..3d4f553ab80
--- /dev/null
+++ b/library/cpp/yt/containers/static_ring_queue-inl.h
@@ -0,0 +1,69 @@
+#ifndef STATIC_RING_QUEUE_INL_H_
+#error "Direct inclusion of this file is not allowed, include static_ring_queue.h"
+// For the sake of sane code completion.
+#include "static_ring_queue.h"
+#endif
+
+#include <library/cpp/yt/assert/assert.h>
+
+#include <util/system/types.h>
+
+#include <algorithm>
+#include <iterator>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T, size_t Capacity>
+void TStaticRingQueue<T, Capacity>::Append(const T* begin, const T* end)
+{
+ // Determine input tail.
+ if (std::distance(begin, end) > static_cast<i64>(Capacity)) {
+ begin = end - Capacity;
+ }
+
+ size_t appendSize = std::distance(begin, end);
+
+ if (Size_ > Capacity - appendSize) {
+ Size_ = Capacity;
+ } else {
+ Size_ += appendSize;
+ }
+
+ EndOffset_ += appendSize;
+ if (EndOffset_ >= Capacity) {
+ EndOffset_ -= Capacity;
+ YT_VERIFY(EndOffset_ < Capacity);
+ }
+
+ size_t tailSize = std::min<size_t>(EndOffset_, appendSize);
+
+ std::copy(end - tailSize, end, Buffer_ + EndOffset_ - tailSize);
+ end -= tailSize;
+ std::copy(begin, end, Buffer_ + Capacity - (end - begin));
+}
+
+template <class T, size_t Capacity>
+void TStaticRingQueue<T, Capacity>::CopyTailTo(size_t copySize, T* begin) const
+{
+ YT_VERIFY(copySize <= Size_);
+
+ if (copySize > EndOffset_) {
+ size_t tailSize = copySize - EndOffset_;
+ std::copy(Buffer_ + Capacity - tailSize, Buffer_ + Capacity, begin);
+ std::copy(Buffer_, Buffer_ + EndOffset_, begin + tailSize);
+ } else {
+ std::copy(Buffer_ + EndOffset_ - copySize, Buffer_ + EndOffset_, begin);
+ }
+}
+
+template <class T, size_t Capacity>
+size_t TStaticRingQueue<T, Capacity>::Size() const
+{
+ return Size_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/containers/static_ring_queue.h b/library/cpp/yt/containers/static_ring_queue.h
new file mode 100644
index 00000000000..acb5c09c4e7
--- /dev/null
+++ b/library/cpp/yt/containers/static_ring_queue.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <cstddef>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T, size_t Capacity>
+class TStaticRingQueue
+{
+public:
+ void Append(const T* begin, const T* end);
+ void CopyTailTo(size_t copySize, T* begin) const;
+ size_t Size() const;
+
+private:
+ T Buffer_[Capacity];
+ size_t EndOffset_ = 0;
+ size_t Size_ = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+#define STATIC_RING_QUEUE_INL_H_
+#include "static_ring_queue-inl.h"
+#undef STATIC_RING_QUEUE_INL_H_
diff --git a/library/cpp/yt/containers/unittests/default_map_ut.cpp b/library/cpp/yt/containers/unittests/default_map_ut.cpp
new file mode 100644
index 00000000000..029fdb041c2
--- /dev/null
+++ b/library/cpp/yt/containers/unittests/default_map_ut.cpp
@@ -0,0 +1,27 @@
+#include <library/cpp/yt/containers/default_map.h>
+
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <util/generic/hash_table.h>
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TDefaultMapTest, Common)
+{
+ TDefaultMap<THashMap<int, std::string>> defaultMap("Hello");
+ EXPECT_EQ(defaultMap[1], "Hello");
+ defaultMap[1].append(", World");
+ EXPECT_EQ(defaultMap[1], "Hello, World");
+ defaultMap.insert({2, "abc"});
+ EXPECT_EQ(defaultMap[2], "abc");
+ EXPECT_EQ(defaultMap.find(3), defaultMap.end());
+ EXPECT_EQ(defaultMap.GetOrDefault(7), "Hello");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/containers/unittests/ring_queue_ut.cpp b/library/cpp/yt/containers/unittests/ring_queue_ut.cpp
new file mode 100644
index 00000000000..a425773650d
--- /dev/null
+++ b/library/cpp/yt/containers/unittests/ring_queue_ut.cpp
@@ -0,0 +1,134 @@
+#include <library/cpp/yt/containers/ring_queue.h>
+
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <library/cpp/yt/memory/new.h>
+#include <library/cpp/yt/memory/ref_counted.h>
+
+#include <cstdlib>
+#include <deque>
+#include <iterator>
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TRingQueueTest, PodRandomOperations)
+{
+ TRingQueue<int> queue;
+ EXPECT_EQ(0, std::ssize(queue));
+ EXPECT_TRUE(queue.empty());
+ EXPECT_EQ(queue.begin(), queue.end());
+
+ const int N = 100000;
+ // We perform same sequence of operations on this deque.
+ std::deque<int> deque;
+
+ for (int i = 0; i < N; ++i) {
+ EXPECT_EQ(queue.size(), deque.size());
+ EXPECT_EQ(queue.empty(), deque.empty());
+ if (!queue.empty()) {
+ EXPECT_EQ(queue.back(), deque.back());
+ EXPECT_EQ(queue.front(), deque.front());
+ }
+
+ int r = rand() % 100;
+ if (r == 0) {
+ queue.clear();
+ deque.clear();
+ EXPECT_EQ(0, std::ssize(queue));
+ EXPECT_TRUE(queue.empty());
+ EXPECT_EQ(queue.begin(), queue.end());
+ } else if (r < 10) {
+ auto queueIterator = queue.begin();
+ auto dequeIterator = deque.begin();
+ while (dequeIterator != deque.end()) {
+ EXPECT_NE(queueIterator, queue.end());
+ EXPECT_EQ(*queueIterator, *dequeIterator);
+ queue.move_forward(queueIterator);
+ ++dequeIterator;
+ }
+ EXPECT_EQ(queueIterator, queue.end());
+ } else if (r < 55 && !queue.empty()) {
+ queue.pop();
+ deque.pop_front();
+ } else {
+ int value = rand();
+ queue.push(value);
+ deque.push_back(value);
+ }
+ }
+}
+
+class TFoo
+ : public virtual TRefCounted
+{
+public:
+ explicit TFoo(int& counter)
+ : Counter_(counter)
+ {
+ ++Counter_;
+ }
+
+ ~TFoo()
+ {
+ --Counter_;
+ }
+
+private:
+ int& Counter_;
+};
+
+DECLARE_REFCOUNTED_TYPE(TFoo)
+DEFINE_REFCOUNTED_TYPE(TFoo)
+
+TEST(TRingQueueTest, TestLifetimeWithRefCount)
+{
+ const int N = 100000;
+ int counter = 0;
+
+ {
+ TRingQueue<TFooPtr> queue;
+
+ for (int i = 0; i < N; ++i) {
+ queue.push(New<TFoo>(counter));
+ EXPECT_EQ(counter, std::ssize(queue));
+ }
+ queue.clear();
+ EXPECT_EQ(counter, 0);
+ }
+
+ {
+ TRingQueue<TFooPtr> queue;
+
+ for (int i = 0; i < N; ++i) {
+ queue.push(New<TFoo>(counter));
+ EXPECT_EQ(counter, std::ssize(queue));
+ queue.pop();
+ EXPECT_EQ(counter, std::ssize(queue));
+ }
+ queue.clear();
+ EXPECT_EQ(counter, 0);
+ }
+
+ {
+ TRingQueue<TFooPtr> queue;
+
+ for (int i = 0; i < N; ++i) {
+ if (!queue.empty() && rand() % 2 == 0) {
+ queue.pop();
+ } else {
+ queue.push(New<TFoo>(counter));
+ }
+ EXPECT_EQ(counter, std::ssize(queue));
+ }
+ queue.clear();
+ EXPECT_EQ(counter, 0);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/containers/unittests/static_ring_queue_ut.cpp b/library/cpp/yt/containers/unittests/static_ring_queue_ut.cpp
new file mode 100644
index 00000000000..b3898463c2b
--- /dev/null
+++ b/library/cpp/yt/containers/unittests/static_ring_queue_ut.cpp
@@ -0,0 +1,130 @@
+#include <library/cpp/yt/containers/static_ring_queue.h>
+
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <deque>
+#include <random>
+#include <vector>
+
+namespace NYT {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+constexpr size_t Capacity = 8;
+
+using TQueue = TStaticRingQueue<int, Capacity>;
+
+std::vector<int> MakeRange(int begin, int end)
+{
+ std::vector<int> result;
+ for (int value = begin; value < end; ++value) {
+ result.push_back(value);
+ }
+ return result;
+}
+
+void Append(TQueue* queue, const std::vector<int>& values)
+{
+ queue->Append(values.data(), values.data() + values.size());
+}
+
+std::vector<int> CopyTail(const TQueue& queue, size_t size)
+{
+ std::vector<int> result(size);
+ queue.CopyTailTo(size, result.data());
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TStaticRingQueueTest, Empty)
+{
+ TQueue queue;
+ EXPECT_EQ(0u, queue.Size());
+ EXPECT_TRUE(CopyTail(queue, 0).empty());
+}
+
+TEST(TStaticRingQueueTest, AppendBelowCapacity)
+{
+ TQueue queue;
+ Append(&queue, MakeRange(0, 3));
+
+ EXPECT_EQ(3u, queue.Size());
+ EXPECT_EQ(MakeRange(0, 3), CopyTail(queue, 3));
+ EXPECT_EQ(MakeRange(1, 3), CopyTail(queue, 2));
+ EXPECT_EQ(MakeRange(2, 3), CopyTail(queue, 1));
+}
+
+TEST(TStaticRingQueueTest, AppendExactlyCapacity)
+{
+ TQueue queue;
+ Append(&queue, MakeRange(0, Capacity));
+
+ EXPECT_EQ(Capacity, queue.Size());
+ EXPECT_EQ(MakeRange(0, Capacity), CopyTail(queue, Capacity));
+}
+
+TEST(TStaticRingQueueTest, SingleAppendAboveCapacityKeepsTail)
+{
+ TQueue queue;
+ Append(&queue, MakeRange(0, 100));
+
+ EXPECT_EQ(Capacity, queue.Size());
+ EXPECT_EQ(MakeRange(100 - Capacity, 100), CopyTail(queue, Capacity));
+}
+
+TEST(TStaticRingQueueTest, AppendsWrapAround)
+{
+ TQueue queue;
+ for (int index = 0; index < 10; ++index) {
+ Append(&queue, MakeRange(3 * index, 3 * index + 3));
+ }
+
+ EXPECT_EQ(Capacity, queue.Size());
+ EXPECT_EQ(MakeRange(30 - Capacity, 30), CopyTail(queue, Capacity));
+}
+
+TEST(TStaticRingQueueTest, EmptyAppendIsNoop)
+{
+ TQueue queue;
+ Append(&queue, MakeRange(0, 5));
+ Append(&queue, {});
+
+ EXPECT_EQ(5u, queue.Size());
+ EXPECT_EQ(MakeRange(0, 5), CopyTail(queue, 5));
+}
+
+TEST(TStaticRingQueueTest, RandomOperations)
+{
+ TQueue queue;
+ std::deque<int> reference;
+
+ std::mt19937 generator(42);
+ int next = 0;
+
+ for (int iteration = 0; iteration < 1000; ++iteration) {
+ int appendSize = generator() % (2 * Capacity + 1);
+ auto values = MakeRange(next, next + appendSize);
+ next += appendSize;
+
+ Append(&queue, values);
+ for (auto value : values) {
+ reference.push_back(value);
+ }
+ while (reference.size() > Capacity) {
+ reference.pop_front();
+ }
+
+ ASSERT_EQ(reference.size(), queue.Size());
+
+ size_t copySize = generator() % (reference.size() + 1);
+ std::vector<int> expected(reference.end() - copySize, reference.end());
+ ASSERT_EQ(expected, CopyTail(queue, copySize));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/containers/unittests/ya.make b/library/cpp/yt/containers/unittests/ya.make
index 4ec4f5ef158..8ee1daf03b6 100644
--- a/library/cpp/yt/containers/unittests/ya.make
+++ b/library/cpp/yt/containers/unittests/ya.make
@@ -5,18 +5,22 @@ INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
SIZE(MEDIUM)
SRCS(
+ default_map_ut.cpp
enum_indexed_array_ut.cpp
expiring_set_ut.cpp
non_empty_ut.cpp
ordered_hash_map_ut.cpp
+ ring_queue_ut.cpp
sentinel_optional_ut.cpp
sharded_set_ut.cpp
slot_map_ut.cpp
+ static_ring_queue_ut.cpp
)
PEERDIR(
library/cpp/yt/containers
library/cpp/yt/compact_containers
+ library/cpp/yt/memory
library/cpp/testing/gtest
)
diff --git a/library/cpp/yt/containers/ya.make b/library/cpp/yt/containers/ya.make
index 8dcea5d170b..ec560898a30 100644
--- a/library/cpp/yt/containers/ya.make
+++ b/library/cpp/yt/containers/ya.make
@@ -8,6 +8,7 @@ SRCS(
PEERDIR(
library/cpp/yt/assert
library/cpp/yt/misc
+ library/cpp/yt/string
)
END()
diff --git a/library/cpp/yt/threading/copyable_atomic.h b/library/cpp/yt/threading/copyable_atomic.h
new file mode 100644
index 00000000000..81d307dc8c3
--- /dev/null
+++ b/library/cpp/yt/threading/copyable_atomic.h
@@ -0,0 +1,47 @@
+#pragma once
+
+#include <atomic>
+
+namespace NYT::NThreading {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! A drop-in replacement for |std::atomic| that can be copied and moved,
+//! which makes it usable as a member of a copyable class.
+/*!
+ * Copying is not atomic: the source is loaded and the result is used to
+ * initialize (or is stored into) the target.
+ */
+template <class T>
+struct TCopyableAtomic
+ : public std::atomic<T>
+{
+ using std::atomic<T>::atomic;
+ using std::atomic<T>::operator=;
+
+ TCopyableAtomic() = default;
+
+ TCopyableAtomic(const TCopyableAtomic& other) noexcept
+ : std::atomic<T>(other.load())
+ { }
+
+ TCopyableAtomic(TCopyableAtomic&& other) noexcept
+ : std::atomic<T>(other.load())
+ { }
+
+ TCopyableAtomic& operator=(const TCopyableAtomic& other) noexcept
+ {
+ this->store(other.load());
+ return *this;
+ }
+
+ TCopyableAtomic& operator=(TCopyableAtomic&& other) noexcept
+ {
+ this->store(other.load());
+ return *this;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT::NThreading
diff --git a/library/cpp/yt/threading/unittests/copyable_atomic_ut.cpp b/library/cpp/yt/threading/unittests/copyable_atomic_ut.cpp
new file mode 100644
index 00000000000..75f16834e36
--- /dev/null
+++ b/library/cpp/yt/threading/unittests/copyable_atomic_ut.cpp
@@ -0,0 +1,86 @@
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <library/cpp/yt/threading/copyable_atomic.h>
+
+#include <vector>
+
+namespace NYT::NThreading {
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TCopyableAtomicTest, LoadStore)
+{
+ TCopyableAtomic<int> atomic;
+ EXPECT_EQ(0, atomic.load());
+
+ atomic.store(42);
+ EXPECT_EQ(42, atomic.load());
+
+ atomic = 7;
+ EXPECT_EQ(7, atomic.load());
+}
+
+TEST(TCopyableAtomicTest, ValueConstructor)
+{
+ TCopyableAtomic<int> atomic(42);
+ EXPECT_EQ(42, atomic.load());
+}
+
+TEST(TCopyableAtomicTest, AtomicApi)
+{
+ TCopyableAtomic<int> atomic(42);
+ EXPECT_EQ(42, atomic.exchange(1));
+ EXPECT_EQ(1, atomic.fetch_add(1));
+
+ int expected = 2;
+ EXPECT_TRUE(atomic.compare_exchange_strong(expected, 100));
+ EXPECT_EQ(100, atomic.load(std::memory_order::relaxed));
+}
+
+TEST(TCopyableAtomicTest, Copy)
+{
+ TCopyableAtomic<int> source(42);
+
+ TCopyableAtomic<int> constructed(source);
+ EXPECT_EQ(42, constructed.load());
+ EXPECT_EQ(42, source.load());
+
+ TCopyableAtomic<int> assigned;
+ assigned = source;
+ EXPECT_EQ(42, assigned.load());
+ EXPECT_EQ(42, source.load());
+
+ source.store(7);
+ EXPECT_EQ(42, constructed.load());
+ EXPECT_EQ(42, assigned.load());
+}
+
+TEST(TCopyableAtomicTest, Move)
+{
+ TCopyableAtomic<int> source(42);
+
+ TCopyableAtomic<int> constructed(std::move(source));
+ EXPECT_EQ(42, constructed.load());
+
+ TCopyableAtomic<int> assigned;
+ assigned = std::move(constructed);
+ EXPECT_EQ(42, assigned.load());
+}
+
+TEST(TCopyableAtomicTest, VectorReallocation)
+{
+ std::vector<TCopyableAtomic<int>> atomics;
+ for (int index = 0; index < 100; ++index) {
+ atomics.emplace_back(index);
+ }
+
+ for (int index = 0; index < 100; ++index) {
+ EXPECT_EQ(index, atomics[index].load());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT::NThreading
diff --git a/library/cpp/yt/threading/unittests/ya.make b/library/cpp/yt/threading/unittests/ya.make
index 01985e9258a..cd5dcb0408c 100644
--- a/library/cpp/yt/threading/unittests/ya.make
+++ b/library/cpp/yt/threading/unittests/ya.make
@@ -3,6 +3,7 @@ GTEST()
INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
SRCS(
+ copyable_atomic_ut.cpp
count_down_latch_ut.cpp
recursive_spin_lock_ut.cpp
rw_spin_lock_ut.cpp