diff options
Diffstat (limited to 'library/cpp')
| -rw-r--r-- | library/cpp/yt/containers/default_map-inl.h | 58 | ||||
| -rw-r--r-- | library/cpp/yt/containers/default_map.h | 64 | ||||
| -rw-r--r-- | library/cpp/yt/containers/ring_queue.h | 382 | ||||
| -rw-r--r-- | library/cpp/yt/containers/static_ring_queue-inl.h | 69 | ||||
| -rw-r--r-- | library/cpp/yt/containers/static_ring_queue.h | 29 | ||||
| -rw-r--r-- | library/cpp/yt/containers/unittests/default_map_ut.cpp | 27 | ||||
| -rw-r--r-- | library/cpp/yt/containers/unittests/ring_queue_ut.cpp | 134 | ||||
| -rw-r--r-- | library/cpp/yt/containers/unittests/static_ring_queue_ut.cpp | 130 | ||||
| -rw-r--r-- | library/cpp/yt/containers/unittests/ya.make | 4 | ||||
| -rw-r--r-- | library/cpp/yt/containers/ya.make | 1 | ||||
| -rw-r--r-- | library/cpp/yt/threading/copyable_atomic.h | 47 | ||||
| -rw-r--r-- | library/cpp/yt/threading/unittests/copyable_atomic_ut.cpp | 86 | ||||
| -rw-r--r-- | library/cpp/yt/threading/unittests/ya.make | 1 |
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 |
