Skip to content

Commit 4cfaf5f

Browse files
hzhdlrpapolukhin
authored andcommitted
feat multi-index-lru: test with custom allocator, docs update
Tests: протестировано CI --- Pull Request resolved: #1165 Co-authored-by: antoshkka <antoshkka@userver.tech> Co-authored-by: antoshkka <antoshkka@userver.tech> commit_hash:028203cca6622a0bb38e9abcc4a4d94bec97ec77
1 parent 6baade5 commit 4cfaf5f

File tree

4 files changed

+100
-6
lines changed

4 files changed

+100
-6
lines changed

libraries/multi-index-lru/include/userver/multi-index-lru/container.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class Container {
2424
: max_size_(max_size)
2525
{}
2626

27+
/// @brief Constructs element in-place
28+
/// @returns pair<iterator, bool>
2729
template <typename... Args>
2830
auto emplace(Args&&... args) {
2931
auto& seq_index = get_sequenced();
@@ -71,6 +73,7 @@ class Container {
7173
return get_index<Tag>().find(key);
7274
}
7375

76+
/// @brief Returns range of matching elements, updates all
7477
template <typename Tag, typename Key>
7578
auto equal_range(const Key& key) {
7679
auto& primary_index = get_index<Tag>();
@@ -87,6 +90,7 @@ class Container {
8790
return std::pair{begin, end};
8891
}
8992

93+
/// @brief Returns range of matching elements without updates
9094
template <typename Tag, typename Key>
9195
auto equal_range_no_update(const Key& key) const {
9296
return get_index<Tag>().equal_range(key);
@@ -158,6 +162,7 @@ class Container {
158162
}
159163
}
160164

165+
/// @brief Returns end iterator for the specified `Tag` index
161166
template <typename Tag>
162167
auto end() const {
163168
return get_index<Tag>().end();

libraries/multi-index-lru/include/userver/multi-index-lru/expirable_container.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ class ExpirableContainer {
137137
return impl::IteratorToValue{container_.template end<Tag>()};
138138
}
139139

140+
/// @brief Removes all expired items from container
140141
void cleanup_expired() {
141142
const auto now = std::chrono::steady_clock::now();
142143

libraries/multi-index-lru/src/container_test.cpp

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,78 @@ UTEST_F(ProductsTest, ProductEviction) {
231231
EXPECT_EQ(cache.find<NameTag>("Mouse"), cache.end<NameTag>());
232232
}
233233

234-
TEST(Snippet, SimpleUsage) {
234+
class ProductsTestWithAllocator : public ProductsTest {
235+
protected:
236+
class Counter {
237+
public:
238+
static std::atomic<size_t> count;
239+
static void increment() { count++; }
240+
static size_t get() { return count.load(); }
241+
static void reset() { count = 0; }
242+
};
243+
244+
template <typename T>
245+
class CountingAllocator : public std::allocator<T> {
246+
public:
247+
CountingAllocator() = default;
248+
template <typename U>
249+
CountingAllocator(const CountingAllocator<U>&) {}
250+
251+
T* allocate(size_t n) {
252+
Counter::increment();
253+
return std::allocator<T>::allocate(n);
254+
}
255+
256+
static size_t get_count() { return Counter::get(); }
257+
static void reset_count() { Counter::reset(); }
258+
259+
template <typename U>
260+
struct rebind { // NOLINT(readability-identifier-naming)
261+
using other = CountingAllocator<U>;
262+
};
263+
};
264+
265+
using ProductCache = multi_index_lru::Container<
266+
Product,
267+
boost::multi_index::indexed_by<
268+
boost::multi_index::ordered_unique<
269+
boost::multi_index::tag<SkuTag>,
270+
boost::multi_index::member<Product, std::string, &Product::sku>>,
271+
boost::multi_index::ordered_unique<
272+
boost::multi_index::tag<NameTag>,
273+
boost::multi_index::member<Product, std::string, &Product::name>>>,
274+
CountingAllocator<Product>>;
275+
};
276+
277+
std::atomic<size_t> ProductsTestWithAllocator::Counter::count{0};
278+
279+
UTEST_F(ProductsTestWithAllocator, AllocationsCheck) {
280+
ProductCache cache(20);
281+
282+
for (int i = 0; i < 1000; ++i) {
283+
cache.insert(Product{"A" + std::to_string(i), "Laptop_" + std::to_string(i), 999.99});
284+
}
285+
const auto first_allocations_count = ProductsTestWithAllocator::CountingAllocator<int>::get_count();
286+
287+
cache.clear();
288+
for (int i = 0; i < 1000; ++i) {
289+
cache.insert(Product{"A" + std::to_string(i), "Laptop_" + std::to_string(i), 999.99});
290+
}
291+
292+
// no extra allocations since nodes are being reused
293+
EXPECT_EQ(first_allocations_count, ProductsTestWithAllocator::CountingAllocator<int>::get_count());
294+
295+
cache.insert(Product{"B_0", "C_0", 999.99});
296+
cache.erase(cache.find<NameTag>("C_0"));
297+
cache.insert(Product{"B_1", "C_1", 999.99});
298+
cache.erase(cache.find<SkuTag>("B_1"));
299+
cache.insert(Product{"B_2", "C_2", 999.99});
300+
301+
// no extra allocations since nodes are being reused
302+
EXPECT_EQ(first_allocations_count, ProductsTestWithAllocator::CountingAllocator<int>::get_count());
303+
}
304+
305+
UTEST(Snippet, SimpleUsage) {
235306
struct MyValueT {
236307
std::string key;
237308
int val;

scripts/docs/en/userver/libraries/multi_index_lru.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
11
# MultiIndex LRU Container
22

3-
## Introduction
4-
53
Generic LRU (Least Recently Used) cache container implementation that combines Boost.MultiIndex for flexible indexing
6-
for efficient LRU tracking. It uses @ref multi_index_lru.
4+
with efficient LRU tracking. The container maintains elements in access order while supporting multiple indexing
5+
strategies through Boost.MultiIndex. The LRU eviction policy automatically removes the least recently accessed items
6+
when capacity is reached.
7+
8+
Two container variants are provided:
9+
- **Container** - Basic LRU cache with capacity management
10+
- **ExpirableContainer** - Extended version with time-based expiration (TTL)
11+
12+
## MultiIndex LRU Implementation Notes
13+
14+
### MultiIndex LRU Node Reuse Strategy
15+
The container maintains a free list of allocated nodes to reduce memory allocations. When items are evicted or erased,
16+
their nodes are moved to the free list and reused for future insertions.
17+
18+
### MultiIndex LRU Thread Safety
19+
This container is **not thread-safe**. External synchronization is required for concurrent access.
20+
21+
### MultiIndex LRU Iterator Invalidation
22+
- Insertions may invalidate iterators if capacity is exceeded and eviction occurs
23+
- Erasures invalidate iterators to the erased element only
24+
- Find operations do not invalidate iterators
25+
- `set_capacity()` may invalidate iterators when reducing capacity
726

8-
The container maintains elements in access order while supporting multiple indexing strategies through Boost.MultiIndex.
9-
The LRU eviction policy automatically removes the least recently accessed items when capacity is reached.
1027

1128
## Usage
1229

0 commit comments

Comments
 (0)