Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,14 @@
/// @file userver/multi-index-lru/container.hpp
/// @brief @copybrief multi_index_lru::Container

#include <boost/multi_index/identity.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index_container.hpp>

#include <cstddef>
#include <tuple>
#include <utility>
#include "impl/mpl_helpers.hpp"

USERVER_NAMESPACE_BEGIN

namespace multi_index_lru {

namespace impl {
template <typename T, typename = std::void_t<>>
inline constexpr bool is_mpl_na = false;

template <typename T>
inline constexpr bool is_mpl_na<T, std::void_t<decltype(std::declval<T>().~na())>> = true;

template <typename... Indices>
struct lazy_add_seq {
using type = boost::multi_index::indexed_by<boost::multi_index::sequenced<>, Indices...>;
};

template <typename... Indices>
struct lazy_add_seq_no_last {
private:
template <std::size_t... I>
static auto makeWithoutLast(std::index_sequence<I...>) {
using Tuple = std::tuple<Indices...>;
return boost::multi_index::indexed_by<boost::multi_index::sequenced<>, std::tuple_element_t<I, Tuple>...>{};
}

public:
using type = decltype(makeWithoutLast(std::make_index_sequence<sizeof...(Indices) - 1>{}));
};

template <typename IndexList>
struct add_seq_index {};

template <typename... Indices>
struct add_seq_index<boost::multi_index::indexed_by<Indices...>> {
using LastType = decltype((Indices{}, ...));

using type = typename std::conditional_t<
is_mpl_na<LastType>,
lazy_add_seq_no_last<Indices...>,
lazy_add_seq<Indices...>>::type;
};

template <typename IndexList>
using add_seq_index_t = typename add_seq_index<IndexList>::type;
} // namespace impl
template <typename Value, typename IndexSpecifierList, typename Allocator = std::allocator<Value>>
class ExpirableContainer;

/// @ingroup userver_containers
///
Expand All @@ -69,44 +23,70 @@ class Container {
{}

template <typename... Args>
bool emplace(Args&&... args) {
auto& seq_index = container_.template get<0>();
auto emplace(Args&&... args) {
auto& seq_index = get_sequensed();
auto result = seq_index.emplace_front(std::forward<Args>(args)...);

if (!result.second) {
seq_index.relocate(seq_index.begin(), result.first);
} else if (seq_index.size() > max_size_) {
seq_index.pop_back();
}
return result.second;
return result;
}

bool insert(const Value& value) { return emplace(value); }
bool insert(const Value& value) { return emplace(value).second; }

bool insert(Value&& value) { return emplace(std::move(value)); }
bool insert(Value&& value) { return emplace(std::move(value)).second; }

template <typename Tag, typename Key>
auto find(const Key& key) {
auto& primary_index = container_.template get<Tag>();
auto& primary_index = get_index<Tag>();
auto it = primary_index.find(key);

if (it != primary_index.end()) {
auto& seq_index = container_.template get<0>();
auto& seq_index = get_sequensed();
auto seq_it = container_.template project<0>(it);
seq_index.relocate(seq_index.begin(), seq_it);
}

return it;
}

template <typename Tag, typename Key>
auto find_no_update(const Key& key) {
return get_index<Tag>().find(key);
}

template <typename Tag, typename Key>
auto equal_range(const Key& key) {
auto& primary_index = get_index<Tag>();

auto [begin, end] = primary_index.equal_range(key);
auto it = begin;

auto& seq_index = get_sequensed();
while (it != end) {
seq_index.relocate(seq_index.begin(), project_to_sequenced(it));
++it;
}

return std::pair{begin, end};
}

template <typename Tag, typename Key>
auto equal_range_no_update(const Key& key) {
return get_index<Tag>().equal_range(key);
}

template <typename Tag, typename Key>
bool contains(const Key& key) {
return this->template find<Tag, Key>(key) != container_.template get<Tag>().end();
return this->template find<Tag, Key>(key) != end<Tag>();
}

template <typename Tag, typename Key>
bool erase(const Key& key) {
return container_.template get<Tag>().erase(key) > 0;
return get_index<Tag>().erase(key) > 0;
}

std::size_t size() const { return container_.size(); }
Expand All @@ -115,7 +95,7 @@ class Container {

void set_capacity(std::size_t new_capacity) {
max_size_ = new_capacity;
auto& seq_index = container_.template get<0>();
auto& seq_index = get_sequensed();
while (container_.size() > max_size_) {
seq_index.pop_back();
}
Expand All @@ -125,16 +105,44 @@ class Container {

template <typename Tag>
auto end() {
return container_.template get<Tag>().end();
return get_index<Tag>().end();
}

private:
using ExtendedIndexSpecifierList = impl::add_seq_index_t<IndexSpecifierList>;
using ExtendedIndexSpecifierList = impl::add_index_t<
boost::multi_index::sequenced<>,
IndexSpecifierList>;

using BoostContainer = boost::multi_index::multi_index_container<Value, ExtendedIndexSpecifierList, Allocator>;

BoostContainer container_;
std::size_t max_size_;

auto &get_sequensed() {
return container_.template get<0>();
}

const auto& get_sequensed() const {
return container_.template get<0>();
}

template <typename Tag>
auto& get_index() {
return container_.template get<Tag>();
}

template <typename Tag>
const auto& get_index() const {
return container_.template get<Tag>();
}

template <typename IterT>
auto project_to_sequenced(IterT it) {
return container_.template project<0>(it);
}

template <typename V, typename I, typename A>
friend class ExpirableContainer;
};
} // namespace multi_index_lru

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#pragma once

/// @file userver/multi-index-lru/expirable_container.hpp
/// @brief @copybrief multi_index_lru::ExpirableContainer

#include "impl/mpl_helpers.hpp"
#include "container.hpp"

#include <userver/utils/assert.hpp>

USERVER_NAMESPACE_BEGIN

namespace multi_index_lru {

/// @ingroup userver_containers
///
/// @brief MultiIndex LRU expirable container
template <typename Value, typename IndexSpecifierList, typename Allocator>
class ExpirableContainer {
public:
explicit ExpirableContainer(size_t max_size,
std::chrono::milliseconds ttl)
: container_(max_size), ttl_(ttl)
{
UASSERT_MSG(ttl.count() > 0, "ttl must be positive");
}

template <typename... Args>
auto emplace(Args&&... args) {
auto result = container_.emplace(std::forward<Args>(args)...);

if (!result.second) {
result.first->last_accessed = std::chrono::steady_clock::now();
}

return result;
}

bool insert(const Value& value) { return emplace(value).second; }

bool insert(Value&& value) { return emplace(std::move(value)).second; }

template <typename Tag, typename Key>
auto find(const Key& key) {
auto now = std::chrono::steady_clock::now();
auto it = container_.template find<Tag, Key>(key);

if (it != container_.template end<Tag>()) {
if (now > it->last_accessed + ttl_) {
container_.template get_index<Tag>().erase(it);
return end<Tag>();
} else {
it->last_accessed = now;
}
}

return impl::TimestampedIteratorWrapper{it};
}

template <typename Tag, typename Key>
auto find_no_update(const Key& key) {
auto it = container_.template find_no_update<Tag, Key>(key);
return impl::TimestampedIteratorWrapper{it};
}

template <typename Tag, typename Key>
auto equal_range(const Key& key) {
auto now = std::chrono::steady_clock::now();
auto& index = container_.template get_index<Tag>();
auto range = container_.template equal_range<Tag, Key>(key);

auto it = range.first;
bool changed = false;

while (it != range.second) {
if (now > it->last_accessed + ttl_) {
it = index.erase(it);
changed = true;
} else {
it->last_accessed = now;
++it;
}
}
if (changed) {
range = index.equal_range(key);
}
return std::pair{impl::TimestampedIteratorWrapper{range.first},
impl::TimestampedIteratorWrapper{range.second}};
}

template <typename Tag, typename Key>
auto equal_range_no_update(const Key& key) {
auto [begin, end] = container_.template equal_range_no_update<Tag, Key>(key);
return std::pair{impl::TimestampedIteratorWrapper{begin},
impl::TimestampedIteratorWrapper{end}};
}

template <typename Tag, typename Key>
bool contains(const Key& key) {
return this->template find<Tag, Key>(key) != this->template end<Tag>();
}

template <typename Tag, typename Key>
bool erase(const Key& key) {
return container_.template erase<Tag, Key>(key);
}

std::size_t size() const {
return container_.size();
}
bool empty() const {
return container_.empty();
}
std::size_t capacity() const { return container_.capacity(); }

void set_capacity(std::size_t new_capacity) {
container_.set_capacity(new_capacity);
}

void clear() {
container_.clear();
}

template <typename Tag>
auto end() {
return impl::TimestampedIteratorWrapper{container_.template end<Tag>()};
}

void cleanup_expired() {
auto now = std::chrono::steady_clock::now();
auto& seq_index = container_.get_sequensed();

while(!seq_index.empty()) {
auto it = seq_index.rbegin();
if (now > it->last_accessed + ttl_) {
seq_index.pop_back();
} else {
break;
}
}
}

private:
using CacheItem = impl::TimestampedValue<Value>;
using CacheContainer = Container<CacheItem, IndexSpecifierList, Allocator>;

CacheContainer container_;
std::chrono::milliseconds ttl_;
};

} // namespace multi_index_lru

USERVER_NAMESPACE_END
Loading
Loading