From c8cf3a09197618155f71db9cfec2f0ed960c2352 Mon Sep 17 00:00:00 2001 From: Vadim Leonov Date: Tue, 10 Mar 2026 19:10:41 +0300 Subject: [PATCH 1/2] bondary tests --- .../multi-index-lru/expirable_container.hpp | 3 +- .../src/expirable_container_test.cpp | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/libraries/multi-index-lru/include/userver/multi-index-lru/expirable_container.hpp b/libraries/multi-index-lru/include/userver/multi-index-lru/expirable_container.hpp index 07c5bbff091f..0f18ed8c000b 100644 --- a/libraries/multi-index-lru/include/userver/multi-index-lru/expirable_container.hpp +++ b/libraries/multi-index-lru/include/userver/multi-index-lru/expirable_container.hpp @@ -22,7 +22,8 @@ class ExpirableContainer { : container_(max_size), ttl_(ttl) { - UASSERT_MSG(ttl.count() > 0, "ttl must be positive"); + UINVARIANT(ttl.count() > 0, "ttl must be positive"); + UINVARIANT(max_size > 0, "capacity must be positive"); } template diff --git a/libraries/multi-index-lru/src/expirable_container_test.cpp b/libraries/multi-index-lru/src/expirable_container_test.cpp index d83ab4c3e7dc..12b4f75f36af 100644 --- a/libraries/multi-index-lru/src/expirable_container_test.cpp +++ b/libraries/multi-index-lru/src/expirable_container_test.cpp @@ -305,6 +305,39 @@ UTEST_F(ExpirableUsersTest, ThreadSafetyBasic) { EXPECT_LE(cache.size(), 100); } +UTEST_F(ExpirableUsersTest, ZeroTTL) { + using namespace std::chrono_literals; + + EXPECT_THROW( + { + UserCacheExpirable cache(10, 0ms); + }, + utils::InvariantError + ); +} + +UTEST_F(ExpirableUsersTest, ZeroCapacity) { + using namespace std::chrono_literals; + + EXPECT_THROW( + { + UserCacheExpirable cache(0, 10s); + }, + utils::InvariantError + ); +} + +UTEST_F(ExpirableUsersTest, NegativeTTL) { + using namespace std::chrono_literals; + + EXPECT_THROW( + { + UserCacheExpirable cache(10, -1ms); + }, + utils::InvariantError + ); +} + } // namespace USERVER_NAMESPACE_END From ca3abbca2e995e3be40ff24e4be3b09e7b80a441 Mon Sep 17 00:00:00 2001 From: Vadim Leonov Date: Thu, 19 Mar 2026 12:49:17 +0300 Subject: [PATCH 2/2] memory pool --- .../userver/multi-index-lru/container.hpp | 27 ++- .../multi-index-lru/expirable_container.hpp | 4 +- .../userver/multi-index-lru/impl/mempool.hpp | 184 ++++++++++++++++++ 3 files changed, 206 insertions(+), 9 deletions(-) create mode 100644 libraries/multi-index-lru/include/userver/multi-index-lru/impl/mempool.hpp diff --git a/libraries/multi-index-lru/include/userver/multi-index-lru/container.hpp b/libraries/multi-index-lru/include/userver/multi-index-lru/container.hpp index d5cda44a3491..a3f7694bbb07 100644 --- a/libraries/multi-index-lru/include/userver/multi-index-lru/container.hpp +++ b/libraries/multi-index-lru/include/userver/multi-index-lru/container.hpp @@ -4,23 +4,31 @@ /// @brief @copybrief multi_index_lru::Container #include "impl/mpl_helpers.hpp" +#include "impl/mempool.hpp" USERVER_NAMESPACE_BEGIN namespace multi_index_lru { -template > +template class ExpirableContainer; /// @ingroup userver_containers /// /// @brief MultiIndex LRU container -template > +template class Container { public: - explicit Container(size_t max_size) + using AllocatorType = FixedPoolAllocator; + + explicit Container(std::size_t max_size) : max_size_(max_size) - {} + , allocator_(max_size + 2) + , container_( + typename BoostContainer::ctor_args_list(), + allocator_ + ) + {} template auto emplace(Args&&... args) { @@ -114,14 +122,19 @@ class Container { } private: - template + template friend class ExpirableContainer; using ExtendedIndexSpecifierList = impl::add_index_t, IndexSpecifierList>; - using BoostContainer = boost::multi_index::multi_index_container; + using BoostContainer = boost::multi_index::multi_index_container< + Value, + ExtendedIndexSpecifierList, + AllocatorType + >; - BoostContainer container_; std::size_t max_size_; + AllocatorType allocator_; + BoostContainer container_; auto& get_sequenced() noexcept { return container_.template get<0>(); } diff --git a/libraries/multi-index-lru/include/userver/multi-index-lru/expirable_container.hpp b/libraries/multi-index-lru/include/userver/multi-index-lru/expirable_container.hpp index 0f18ed8c000b..ec7afb98fb77 100644 --- a/libraries/multi-index-lru/include/userver/multi-index-lru/expirable_container.hpp +++ b/libraries/multi-index-lru/include/userver/multi-index-lru/expirable_container.hpp @@ -15,7 +15,7 @@ namespace multi_index_lru { /// @ingroup userver_containers /// /// @brief MultiIndex LRU expirable container -template +template class ExpirableContainer { public: explicit ExpirableContainer(size_t max_size, std::chrono::milliseconds ttl) @@ -138,7 +138,7 @@ class ExpirableContainer { private: using CacheItem = impl::TimestampedValue; - using CacheContainer = Container; + using CacheContainer = Container; CacheContainer container_; std::chrono::milliseconds ttl_; diff --git a/libraries/multi-index-lru/include/userver/multi-index-lru/impl/mempool.hpp b/libraries/multi-index-lru/include/userver/multi-index-lru/impl/mempool.hpp new file mode 100644 index 000000000000..3f3e71caaaa4 --- /dev/null +++ b/libraries/multi-index-lru/include/userver/multi-index-lru/impl/mempool.hpp @@ -0,0 +1,184 @@ +#pragma once + +#include +#include +#include +#include + +USERVER_NAMESPACE_BEGIN + +namespace multi_index_lru { + +static std::size_t align_up(std::size_t size) noexcept { + constexpr std::size_t alignment = alignof(std::max_align_t); + return (size + alignment - 1) & ~(alignment - 1); +} + +class FixedPool { +public: + explicit FixedPool(std::size_t capacity, std::size_t elem_size) + : capacity_(capacity) + , element_size_(align_up(elem_size)) + , storage_(nullptr) + , free_head_(nullptr) { + + if (capacity_ == 0) { + return; + } + + std::size_t total_size = capacity_ * element_size_; + + storage_ = static_cast(::operator new(total_size)); + + char* current = storage_; + free_head_ = nullptr; + + for (std::size_t i = 0; i < capacity_; ++i) { + + void** next_ptr = reinterpret_cast(current); + *next_ptr = free_head_; + free_head_ = current; + + current += element_size_; + } + } + + ~FixedPool() { + ::operator delete(storage_); + } + + FixedPool(const FixedPool&) = delete; + FixedPool& operator=(const FixedPool&) = delete; + + void* allocate() { + if (!free_head_) { + std::cerr << "ERROR: FixedPool out of memory! capacity=" << capacity_ << std::endl; + throw std::bad_alloc(); + } + + void* block = free_head_; + + void** next_ptr = static_cast(block); + free_head_ = *next_ptr; + + return block; + } + + void deallocate(void* ptr) noexcept { + if (!ptr) { + return; + } + + char* char_ptr = static_cast(ptr); + char* storage_end = storage_ + capacity_ * element_size_; + + if (char_ptr < storage_ || char_ptr >= storage_end) { + std::cerr << "ERROR: FixedPool deallocating pointer not from pool!" << std::endl; + std::cerr << " ptr: " << ptr << " is outside [" + << static_cast(storage_) << ", " + << static_cast(storage_end) << ")" << std::endl; + return; + } + + void** next_ptr = static_cast(ptr); + *next_ptr = free_head_; + free_head_ = ptr; + } + + std::size_t capacity() const noexcept { return capacity_; } + std::size_t element_size() const noexcept { return element_size_; } + +private: + std::size_t capacity_; + std::size_t element_size_; + char* storage_; + void* free_head_; +}; + +template +class FixedPoolAllocator { +public: + using value_type = T; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using propagate_on_container_move_assignment = std::true_type; + using is_always_equal = std::false_type; + + FixedPoolAllocator() noexcept + : pool_(nullptr) {} + + explicit FixedPoolAllocator(std::size_t capacity) noexcept + : FixedPoolAllocator(capacity, sizeof(T)) {} + + FixedPoolAllocator(std::size_t capacity, std::size_t element_size) noexcept + : pool_(std::make_shared(capacity, element_size)) {} + + FixedPoolAllocator(const FixedPoolAllocator& other) = delete; + + FixedPoolAllocator(FixedPoolAllocator&& other) = delete; + + template + FixedPoolAllocator(const FixedPoolAllocator& other) noexcept { + pool_ = std::make_shared( + other.pool_->capacity(), + sizeof(T) + ); + } + + ~FixedPoolAllocator() {} + + T* allocate(size_type n) { + if (n != 1) { + return static_cast(::operator new(n * sizeof(T))); + } + + if (!pool_) { + std::cerr << " ERROR: pool_ is null!" << std::endl; + throw std::bad_alloc(); + } + + void* ptr = pool_->allocate(); + + T* result = static_cast(ptr); + + return result; + } + + void deallocate(T* ptr, size_type n) noexcept { + + if (n != 1) { + ::operator delete(ptr); + return; + } + + if (pool_) { + pool_->deallocate(ptr); + } + } + + template struct rebind { using other = FixedPoolAllocator; }; + + template + bool operator==(const FixedPoolAllocator& other) const noexcept { + bool eq = (pool_ == other.pool_); + return eq; + } + + template + bool operator!=(const FixedPoolAllocator& other) const noexcept { + return !(*this == other); + } + + std::size_t get_element_size() const noexcept { + return pool_ ? pool_->element_size() : 0; + } + +private: + template friend class FixedPoolAllocator; + + std::shared_ptr pool_; +}; + +} // namespace multi_index_lru + +USERVER_NAMESPACE_END \ No newline at end of file