Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class Container {
: max_size_(max_size)
{}

// Constructs element in-place, returns pair<iterator, bool>
template <typename... Args>
auto emplace(Args&&... args) {
auto& seq_index = get_sequenced();
Expand Down Expand Up @@ -71,6 +72,7 @@ class Container {
return get_index<Tag>().find(key);
}

// Returns range of matching elements, updates all
template <typename Tag, typename Key>
auto equal_range(const Key& key) {
auto& primary_index = get_index<Tag>();
Expand All @@ -87,6 +89,7 @@ class Container {
return std::pair{begin, end};
}

// Returns range of matching elements without updates
template <typename Tag, typename Key>
auto equal_range_no_update(const Key& key) const {
return get_index<Tag>().equal_range(key);
Expand Down Expand Up @@ -158,6 +161,7 @@ class Container {
}
}

// Returns end iterator for specified index
template <typename Tag>
auto end() const {
return get_index<Tag>().end();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class ExpirableContainer {
return impl::IteratorToValue{container_.template end<Tag>()};
}

// Removes all expired items from container
void cleanup_expired() {
const auto now = std::chrono::steady_clock::now();

Expand Down
66 changes: 65 additions & 1 deletion libraries/multi-index-lru/src/container_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,71 @@ UTEST_F(ProductsTest, ProductEviction) {
EXPECT_EQ(cache.find<NameTag>("Mouse"), cache.end<NameTag>());
}

TEST(Snippet, SimpleUsage) {
class ProductsTestWithAllocator : public ProductsTest {
protected:

class Counter {
public:
static std::atomic<size_t> count;
static void increment() { count++; }
static size_t get() { return count.load(); }
static void reset() { count = 0; }
};

template <typename T>
class CountingAllocator : public std::allocator<T> {
public:
CountingAllocator() = default;
template <typename U>
CountingAllocator(const CountingAllocator<U>&) {}

T* allocate(size_t n) {
Counter::increment();
return std::allocator<T>::allocate(n);
}

static size_t get_count() {return Counter::get();}
static void reset_count() {Counter::reset();}

template <typename U>
struct rebind {
using other = CountingAllocator<U>;
};
};

using ProductCache = multi_index_lru::Container<
Product,
boost::multi_index::indexed_by<
boost::multi_index::ordered_unique<
boost::multi_index::tag<SkuTag>,
boost::multi_index::member<Product, std::string, &Product::sku>>,
boost::multi_index::ordered_unique<
boost::multi_index::tag<NameTag>,
boost::multi_index::member<Product, std::string, &Product::name>>>,
CountingAllocator<Product>>;
};

std::atomic<size_t> ProductsTestWithAllocator::Counter::count{0};

UTEST_F(ProductsTestWithAllocator, AllocationsCheck) {
ProductCache cache(20);

for (int i = 0; i < 1000; ++i) {
cache.insert(Product{"A" + std::to_string(i), "Laptop_" + std::to_string(i), 999.99});
}
auto firstAllocationsCount = ProductsTestWithAllocator::CountingAllocator<int>::get_count();

cache.clear();
for (int i = 0; i < 1000; ++i) {
cache.insert(Product{"A" + std::to_string(i), "Laptop_" + std::to_string(i), 999.99});
}
auto secondAllocationsCount = ProductsTestWithAllocator::CountingAllocator<int>::get_count();

// no extra allocations since nodes are being reused
EXPECT_EQ(firstAllocationsCount, secondAllocationsCount);
}

UTEST(Snippet, SimpleUsage) {
struct MyValueT {
std::string key;
int val;
Expand Down
23 changes: 19 additions & 4 deletions scripts/docs/en/userver/libraries/multi_index_lru.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,26 @@

## Introduction

Generic LRU (Least Recently Used) cache container implementation that combines Boost.MultiIndex for flexible indexing
for efficient LRU tracking. It uses @ref multi_index_lru.
Generic LRU (Least Recently Used) cache container implementation that combines Boost.MultiIndex for flexible indexing with efficient LRU tracking. The container maintains elements in access order while supporting multiple indexing strategies through Boost.MultiIndex. The LRU eviction policy automatically removes the least recently accessed items when capacity is reached.

Two container variants are provided:
- **Container** - Basic LRU cache with capacity management
- **ExpirableContainer** - Extended version with time-based expiration (TTL)

## Implementation Notes

### Node Reuse Strategy
The container maintains a free list of allocated nodes to reduce memory allocations. When items are evicted or erased, their nodes are moved to the free list and reused for future insertions.

### Thread Safety
This container is **not thread-safe**. External synchronization is required for concurrent access.

### Iterator Invalidation
- Insertions may invalidate iterators if capacity is exceeded and eviction occurs
- Erasures invalidate iterators to the erased element only
- Find operations do not invalidate iterators
- `set_capacity()` may invalidate iterators when reducing capacity

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

## Usage

Expand Down
Loading