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 @@ -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 <typename Value, typename IndexSpecifierList, typename Allocator = std::allocator<Value>>
template <typename Value, typename IndexSpecifierList>
class ExpirableContainer;

/// @ingroup userver_containers
///
/// @brief MultiIndex LRU container
template <typename Value, typename IndexSpecifierList, typename Allocator = std::allocator<Value>>
template <typename Value, typename IndexSpecifierList>
class Container {
public:
explicit Container(size_t max_size)
using AllocatorType = FixedPoolAllocator<Value>;

explicit Container(std::size_t max_size)
: max_size_(max_size)
{}
, allocator_(max_size + 2)
, container_(
typename BoostContainer::ctor_args_list(),
allocator_
)
{}

template <typename... Args>
auto emplace(Args&&... args) {
Expand Down Expand Up @@ -114,14 +122,19 @@ class Container {
}

private:
template <typename V, typename I, typename A>
template <typename V, typename I>
friend class ExpirableContainer;

using ExtendedIndexSpecifierList = impl::add_index_t<boost::multi_index::sequenced<>, IndexSpecifierList>;
using BoostContainer = boost::multi_index::multi_index_container<Value, ExtendedIndexSpecifierList, Allocator>;
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>(); }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ namespace multi_index_lru {
/// @ingroup userver_containers
///
/// @brief MultiIndex LRU expirable container
template <typename Value, typename IndexSpecifierList, typename Allocator>
template <typename Value, typename IndexSpecifierList>
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");
UINVARIANT(ttl.count() > 0, "ttl must be positive");
UINVARIANT(max_size > 0, "capacity must be positive");
}

template <typename... Args>
Expand Down Expand Up @@ -137,7 +138,7 @@ class ExpirableContainer {

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

CacheContainer container_;
std::chrono::milliseconds ttl_;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#pragma once

#include <memory>
#include <cstddef>
#include <cassert>
#include <iostream>

USERVER_NAMESPACE_BEGIN

namespace multi_index_lru {

static std::size_t align_up(std::size_t size) noexcept {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

тоже люблю статические функции, но лучше положить в анонимный неймспейс

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explicit лишний

: 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<char*>(::operator new(total_size));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

нельзя ли в списках инициализации это сделать? и в умный пойнтер обернуть?


char* current = storage_;
free_head_ = nullptr;

for (std::size_t i = 0; i < capacity_; ++i) {

void** next_ptr = reinterpret_cast<void**>(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<void**>(block);
free_head_ = *next_ptr;

return block;
}

void deallocate(void* ptr) noexcept {
if (!ptr) {
return;
}

char* char_ptr = static_cast<char*>(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<void*>(storage_) << ", "
<< static_cast<void*>(storage_end) << ")" << std::endl;
return;
}

void** next_ptr = static_cast<void**>(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 <typename T>
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<FixedPool>(capacity, element_size)) {}

FixedPoolAllocator(const FixedPoolAllocator& other) = delete;

FixedPoolAllocator(FixedPoolAllocator&& other) = delete;

template <typename U>
FixedPoolAllocator(const FixedPoolAllocator<U>& other) noexcept {
pool_ = std::make_shared<FixedPool>(
other.pool_->capacity(),
sizeof(T)
);
}

~FixedPoolAllocator() {}

T* allocate(size_type n) {
if (n != 1) {
return static_cast<T*>(::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<T*>(ptr);

return result;
}

void deallocate(T* ptr, size_type n) noexcept {

if (n != 1) {
::operator delete(ptr);
return;
}

if (pool_) {
pool_->deallocate(ptr);
}
}

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

template <typename U>
bool operator==(const FixedPoolAllocator<U>& other) const noexcept {
bool eq = (pool_ == other.pool_);
return eq;
}

template <typename U>
bool operator!=(const FixedPoolAllocator<U>& other) const noexcept {
return !(*this == other);
}

std::size_t get_element_size() const noexcept {
return pool_ ? pool_->element_size() : 0;
}

private:
template <typename U> friend class FixedPoolAllocator;

std::shared_ptr<FixedPool> pool_;
};

} // namespace multi_index_lru

USERVER_NAMESPACE_END
33 changes: 33 additions & 0 deletions libraries/multi-index-lru/src/expirable_container_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading