/* * Copyright (C) 2020-present ScyllaDB */ /* * SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0 */ #pragma once #include "utils/assert.hh" #include #include class evictable { friend class lru; // For bookkeeping, we want the unlinking of evictables to be explicit. // E.g. if the cache's internal data structure consists of multiple lists, we would // like to know which list is an element being removed from. // Therefore, we are using auto_unlink only to be able to call unlink() in the move constructor // and we do NOT rely on automatic unlinking in _lru_link's destructor. // It's the programmer's responsibility. to call lru::remove on the evictable before its destruction. // Failure to do so is a bug, and it will trigger an assertion in the destructor. protected: using link_base = boost::intrusive::list_member_hook>; struct lru_link_type : link_base { lru_link_type() noexcept = default; lru_link_type(lru_link_type&& o) noexcept { swap_nodes(o); } }; static_assert(std::is_nothrow_constructible_v); private: lru_link_type _lru_link; protected: // Prevent destruction via evictable pointer. LRU is not aware of allocation strategy. // Prevent destruction of a linked evictable. While we could unlink the evictable here // in the destructor, we can't perform proper accounting for that without access to the // head of the containing list. ~evictable() { SCYLLA_ASSERT(!_lru_link.is_linked()); } evictable() = default; evictable(evictable&&) noexcept = default; public: virtual void on_evicted() noexcept = 0; // Used for testing to avoid cascading eviction of the containing object. virtual void on_evicted_shallow() noexcept { on_evicted(); } bool is_linked() const { return _lru_link.is_linked(); } void swap(evictable& o) noexcept { _lru_link.swap_nodes(o._lru_link); } virtual bool is_index() const noexcept { return false; } }; // Sstable index cache shares memory with the data cache. // To prevent index entries from depriving the data cache of memory, // there is a limit (index_cache_fraction) on the total fraction of cache usable // by index entries. // // To maintain this limit, index entries might have to be evicted outside of the regular LRU order. // Therefore they are linked both in the common LRU list and in a separate LRU list for index entries. class index_evictable : public evictable { friend class lru; evictable::lru_link_type _index_lru_link; bool is_index() const noexcept override { return true; } }; // Implements LRU cache replacement for row cache and sstable index cache. class lru { private: using lru_type = boost::intrusive::list, boost::intrusive::constant_time_size>; // we need this to have bi::auto_unlink on hooks. lru_type _list; // See the comment to index_evictable. using index_lru_type = boost::intrusive::list, boost::intrusive::constant_time_size>; // we need this to have bi::auto_unlink on hooks. index_lru_type _index_list; using reclaiming_result = seastar::memory::reclaiming_result; public: ~lru() { while (!_list.empty()) { evictable& e = _list.front(); remove(e); e.on_evicted(); } } void remove(evictable& e) noexcept { _list.erase(_list.iterator_to(e)); if (e.is_index()) { _index_list.erase(_index_list.iterator_to(static_cast(e))); } } void add(evictable& e) noexcept { _list.push_back(e); if (e.is_index()) { _index_list.push_back(static_cast(e)); } } // Like add(e) but makes sure that e is evicted right before "more_recent" in the absence of later touches. void add_before(evictable& more_recent, evictable& e) noexcept { _list.insert(_list.iterator_to(more_recent), e); } void touch(evictable& e) noexcept { remove(e); add(e); } // Evicts a single element from the LRU template reclaiming_result do_evict(bool should_evict_index) noexcept { if (_list.empty()) { return reclaiming_result::reclaimed_nothing; } evictable& e = (should_evict_index && !_index_list.empty()) ? _index_list.front() : _list.front(); remove(e); if constexpr (!Shallow) { e.on_evicted(); } else { e.on_evicted_shallow(); } return reclaiming_result::reclaimed_something; } // Evicts a single element from the LRU. reclaiming_result evict(bool should_evict_index = false) noexcept { return do_evict(should_evict_index); } // Evicts a single element from the LRU. // Will call on_evicted_shallow() instead of on_evicted(). reclaiming_result evict_shallow() noexcept { return do_evict(false); } // Evicts all elements. // May stall the reactor, use only in tests. void evict_all() { while (evict() == reclaiming_result::reclaimed_something) {} } };