summaryrefslogtreecommitdiffstats
path: root/src/common/intrusive_lru.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
commite6918187568dbd01842d8d1d2c808ce16a894239 (patch)
tree64f88b554b444a49f656b6c656111a145cbbaa28 /src/common/intrusive_lru.h
parentInitial commit. (diff)
downloadceph-upstream/18.2.2.tar.xz
ceph-upstream/18.2.2.zip
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/common/intrusive_lru.h')
-rw-r--r--src/common/intrusive_lru.h222
1 files changed, 222 insertions, 0 deletions
diff --git a/src/common/intrusive_lru.h b/src/common/intrusive_lru.h
new file mode 100644
index 000000000..e8c3cda3e
--- /dev/null
+++ b/src/common/intrusive_lru.h
@@ -0,0 +1,222 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#pragma once
+
+#include <boost/intrusive_ptr.hpp>
+#include <boost/intrusive/set.hpp>
+#include <boost/intrusive/list.hpp>
+
+namespace ceph::common {
+
+/**
+ * intrusive_lru: lru implementation with embedded map and list hook
+ *
+ * Note, this implementation currently is entirely thread-unsafe.
+ */
+
+template <typename K, typename V, typename VToK>
+struct intrusive_lru_config {
+ using key_type = K;
+ using value_type = V;
+ using key_of_value = VToK;
+};
+
+template <typename Config>
+class intrusive_lru;
+
+template <typename Config>
+class intrusive_lru_base;
+
+template <typename Config>
+void intrusive_ptr_add_ref(intrusive_lru_base<Config> *p);
+
+template <typename Config>
+void intrusive_ptr_release(intrusive_lru_base<Config> *p);
+
+
+template <typename Config>
+class intrusive_lru_base {
+ unsigned use_count = 0;
+
+ // null if unreferenced
+ intrusive_lru<Config> *lru = nullptr;
+
+public:
+ boost::intrusive::set_member_hook<> set_hook;
+ boost::intrusive::list_member_hook<> list_hook;
+
+ using Ref = boost::intrusive_ptr<typename Config::value_type>;
+ using lru_t = intrusive_lru<Config>;
+
+ friend intrusive_lru<Config>;
+ friend void intrusive_ptr_add_ref<>(intrusive_lru_base<Config> *);
+ friend void intrusive_ptr_release<>(intrusive_lru_base<Config> *);
+
+ virtual ~intrusive_lru_base() {}
+};
+
+template <typename Config>
+class intrusive_lru {
+ using base_t = intrusive_lru_base<Config>;
+ using K = typename Config::key_type;
+ using T = typename Config::value_type;
+ using TRef = typename base_t::Ref;
+
+ using lru_set_option_t = boost::intrusive::member_hook<
+ base_t,
+ boost::intrusive::set_member_hook<>,
+ &base_t::set_hook>;
+
+ using VToK = typename Config::key_of_value;
+ struct VToKWrapped {
+ using type = typename VToK::type;
+ const type &operator()(const base_t &obc) {
+ return VToK()(static_cast<const T&>(obc));
+ }
+ };
+ using lru_set_t = boost::intrusive::set<
+ base_t,
+ lru_set_option_t,
+ boost::intrusive::key_of_value<VToKWrapped>
+ >;
+ lru_set_t lru_set;
+
+ using lru_list_t = boost::intrusive::list<
+ base_t,
+ boost::intrusive::member_hook<
+ base_t,
+ boost::intrusive::list_member_hook<>,
+ &base_t::list_hook>>;
+ lru_list_t unreferenced_list;
+
+ size_t lru_target_size = 0;
+
+ void evict() {
+ while (!unreferenced_list.empty() &&
+ lru_set.size() > lru_target_size) {
+ auto &b = unreferenced_list.front();
+ assert(!b.lru);
+ unreferenced_list.pop_front();
+ lru_set.erase_and_dispose(
+ lru_set.iterator_to(b),
+ [](auto *p) { delete p; }
+ );
+ }
+ }
+
+ void access(base_t &b) {
+ if (b.lru)
+ return;
+ unreferenced_list.erase(lru_list_t::s_iterator_to(b));
+ b.lru = this;
+ }
+
+ void insert(base_t &b) {
+ assert(!b.lru);
+ lru_set.insert(b);
+ b.lru = this;
+ evict();
+ }
+
+ void unreferenced(base_t &b) {
+ assert(b.lru);
+ unreferenced_list.push_back(b);
+ b.lru = nullptr;
+ evict();
+ }
+
+public:
+ /**
+ * Returns the TRef corresponding to k if it exists or
+ * creates it otherwise. Return is:
+ * std::pair(reference_to_val, found)
+ */
+ std::pair<TRef, bool> get_or_create(const K &k) {
+ typename lru_set_t::insert_commit_data icd;
+ auto [iter, missing] = lru_set.insert_check(
+ k,
+ icd);
+ if (missing) {
+ auto ret = new T(k);
+ lru_set.insert_commit(*ret, icd);
+ insert(*ret);
+ return {TRef(ret), false};
+ } else {
+ access(*iter);
+ return {TRef(static_cast<T*>(&*iter)), true};
+ }
+ }
+
+ /*
+ * Clears unreferenced elements from the lru set [from, to]
+ */
+ void clear_range(
+ const K& from,
+ const K& to) {
+ auto from_iter = lru_set.lower_bound(from);
+ auto to_iter = lru_set.upper_bound(to);
+ for (auto i = from_iter; i != to_iter; ) {
+ if (!(*i).lru) {
+ unreferenced_list.erase(lru_list_t::s_iterator_to(*i));
+ i = lru_set.erase_and_dispose(i, [](auto *p)
+ { delete p; } );
+ } else {
+ i++;
+ }
+ }
+ }
+
+ template <class F>
+ void for_each(F&& f) {
+ for (auto& v : lru_set) {
+ access(v);
+ f(TRef{static_cast<T*>(&v)});
+ }
+ }
+
+ /**
+ * Returns the TRef corresponding to k if it exists or
+ * nullptr otherwise.
+ */
+ TRef get(const K &k) {
+ if (auto iter = lru_set.find(k); iter != std::end(lru_set)) {
+ access(*iter);
+ return TRef(static_cast<T*>(&*iter));
+ } else {
+ return nullptr;
+ }
+ }
+
+ void set_target_size(size_t target_size) {
+ lru_target_size = target_size;
+ evict();
+ }
+
+ ~intrusive_lru() {
+ set_target_size(0);
+ }
+
+ friend void intrusive_ptr_add_ref<>(intrusive_lru_base<Config> *);
+ friend void intrusive_ptr_release<>(intrusive_lru_base<Config> *);
+};
+
+template <typename Config>
+void intrusive_ptr_add_ref(intrusive_lru_base<Config> *p) {
+ assert(p);
+ assert(p->lru);
+ p->use_count++;
+}
+
+template <typename Config>
+void intrusive_ptr_release(intrusive_lru_base<Config> *p) {
+ assert(p);
+ assert(p->use_count > 0);
+ --p->use_count;
+ if (p->use_count == 0) {
+ p->lru->unreferenced(*p);
+ }
+}
+
+
+}