Skip to content
Open
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 @@ -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,7 +23,7 @@ class Container {
{}

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

Expand All @@ -78,12 +32,12 @@ class Container {
} 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) {
Expand Down Expand Up @@ -129,12 +83,35 @@ class Container {
}

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() {
return container_.template get<Tag>();
}

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

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,169 @@
#pragma once

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

#include <functional>
#include <cassert>
#include <shared_mutex>
#include <mutex>

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

#include <userver/utils/async.hpp>
#include <userver/utils/rand.hpp>
#include <userver/engine/mutex.hpp>
#include <userver/engine/shared_mutex.hpp>
#include <userver/engine/task/task_with_result.hpp>
#include <userver/engine/sleep.hpp>
#include <userver/engine/task/cancel.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,
std::chrono::milliseconds cleanup_interval = std::chrono::milliseconds(60))
: container_(max_size), ttl_(ttl), cleanup_interval_(cleanup_interval)
{
assert(ttl.count() > 0 && "ttl must be positive");
assert(cleanup_interval.count() > 0 && "cleanup_interval must be positive");
}

~ExpirableContainer() {
stop_cleanup();
}

template <typename... Args>
auto emplace(Args&&... args) {
std::lock_guard<userver::engine::SharedMutex> lock(mutex_);

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

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

start_cleanup();

return result;
}

template <typename Tag, typename Key>
auto find(const Key& key) {
std::lock_guard<userver::engine::SharedMutex> lock(mutex_);
auto it = container_.template find<Tag, Key>(key);

if (it != container_.template end<Tag>()) {
if (std::chrono::steady_clock::now() > it->last_accessed + ttl_) {
container_.template get<Tag>().erase(it);
return impl::TimestampedIteratorWrapper{container_.template end<Tag>()};
}

it->last_accessed = std::chrono::steady_clock::now();
}

return impl::TimestampedIteratorWrapper{it};
}

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

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

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

template <typename Tag, typename Key>
bool erase(const Key& key) {
std::lock_guard<userver::engine::SharedMutex> lock(mutex_);
return container_.template erase<Tag, Key>(key);
}

std::size_t size() const {
std::shared_lock<userver::engine::SharedMutex> lock(mutex_);
return container_.size();
}
bool empty() const {
std::shared_lock<userver::engine::SharedMutex> lock(mutex_);
return container_.empty();
}
std::size_t capacity() const { return container_.capacity(); }

void set_capacity(std::size_t new_capacity) {
std::lock_guard<userver::engine::SharedMutex> lock(mutex_);
container_.set_capacity(new_capacity);
}

void clear() {
std::lock_guard<userver::engine::SharedMutex> lock(mutex_);
container_.clear();
}

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

private:
using CacheItem = impl::TimestampedValue<Value>;
using ExtendedIndexSpecifierList = impl::add_index_t<
boost::multi_index::sequenced<>,
IndexSpecifierList>;
using BoostContainer = Container<CacheItem, IndexSpecifierList, Allocator>;

void cleanup() {
std::lock_guard<userver::engine::SharedMutex> lock(mutex_);
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;
}
}
}

void start_cleanup() {
if (cleanup_task_.IsValid() && !cleanup_task_.IsFinished()) {
return;
}

cleanup_task_ = userver::utils::Async("lru_cleanup", [this] {
while (!userver::engine::current_task::ShouldCancel()) {
userver::engine::SleepFor(cleanup_interval_);
this->cleanup();
}
});
}

void stop_cleanup() {
if (cleanup_task_.IsValid()) {
cleanup_task_.RequestCancel();
cleanup_task_.Wait();
}
}

BoostContainer container_;
std::chrono::milliseconds ttl_;
std::chrono::milliseconds cleanup_interval_;
mutable userver::engine::SharedMutex mutex_;
userver::engine::Task cleanup_task_;
};


} // namespace multi_index_lru

USERVER_NAMESPACE_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#pragma once

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

#include <cstddef>
#include <tuple>
#include <utility>
#include <chrono>

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 IndexType, typename... Indices>
struct lazy_add_index {
using type = boost::multi_index::indexed_by<IndexType, Indices...>;
};

template <typename IndexType, typename... Indices>
struct lazy_add_index_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<IndexType, std::tuple_element_t<I, Tuple>...>{};
}

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

template <typename IndexType, typename IndexList>
struct add_index {};

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

using type = typename std::conditional_t<
is_mpl_na<LastType>,
lazy_add_index_no_last<IndexType, Indices...>,
lazy_add_index<IndexType, Indices...>>::type;
};

template <typename IndexType, typename IndexList>
using add_index_t = typename add_index<IndexType, IndexList>::type;

template<typename Value>
struct TimestampedValue {
Value value;
mutable std::chrono::steady_clock::time_point last_accessed;

TimestampedValue() = default;

explicit TimestampedValue(const Value& val)
: value(val), last_accessed(std::chrono::steady_clock::now()) {}

explicit TimestampedValue(Value&& val)
: value(std::move(val)), last_accessed(std::chrono::steady_clock::now()) {}

operator Value&() { return value; }
operator const Value&() const { return value; }

Value* operator->() { return &value; }
const Value* operator->() const { return &value; }

Value& operator*() {return value; }
const Value& operator*() const {return value; }

Value& get() { return value; }
const Value& get() const { return value; }
};

template <typename Iterator>
class TimestampedIteratorWrapper : public Iterator {
public:
using Iterator::Iterator;
TimestampedIteratorWrapper(Iterator iter) : Iterator(std::move(iter)) {}

auto operator->() {
return *(this->Iterator::operator->());
}

auto operator->() const {
return *(this->Iterator::operator->());
}
};
} // namespace impl
} // namespace multi_index_lru

USERVER_NAMESPACE_END
Loading
Loading