class intrusive_lru_base {
/* object invariants
*
- * intrusive_lru objects may be in one of two states:
+ * intrusive_lru objects may be in one of three states:
* 1. referenced
+ * - accessible via intrusive_lru
* - intrusive_lru_base::lru is points to parent intrusive_lru
- * - present in lru_set
+ * - present in intrusive_lru::lru_set
+ * - absent from intrusive_lru::unreferenced_list
* - use_count > 0
* - not eligible for eviction
* - intrusive_lru_release may be invoked externally
* 2. unreferenced
+ * - accessible via intrusive_lru
* - intrusive_lru_base::lru is null
- * - present in lru_set
+ * - present in intrusive_lru::lru_set
* - present in intrusive_lru::unreferenced_list
* - use_count == 0
* - eligible for eviction
* - intrusive_lru_release cannot be invoked
+ * 3. invalidated
+ * - inaccessible via intrusive_lru
+ * - intrusive_lru_base::lru is null
+ * - absent from intrusive_lru::lru_set
+ * - absent from intrusive_lru::unreferenced_list
+ * - use_count > 0
+ * - intrusive_lru_release may be invoked externally
*/
unsigned use_count = 0;
return static_cast<bool>(lru);
}
bool is_unreferenced() const {
- return !is_referenced();
+ return !is_referenced() && use_count == 0;
+ }
+ bool is_invalidated() const {
+ return !is_referenced() && use_count > 0;
}
boost::intrusive::set_member_hook<> set_hook;
boost::intrusive::list_member_hook<> list_hook;
}
}
+ /// drop all elements from lru, invoke f on any with outstanding references
+ template <typename F>
+ void clear(F &&f) {
+ evict(0);
+ assert(unreferenced_list.empty());
+ for (auto &i: lru_set) {
+ std::invoke(f, static_cast<T&>(i));
+ i.lru = nullptr;
+ assert(i.is_invalidated());
+ }
+ lru_set.clear_and_dispose([](auto *i){
+ assert(i->use_count > 0); /* don't delete, still has a ref count */
+ });
+ }
+
template <class F>
void for_each(F&& f) {
for (auto& v : lru_set) {
template <typename Config>
void intrusive_ptr_add_ref(intrusive_lru_base<Config> *p) {
assert(p);
- assert(p->lru);
p->use_count++;
+ assert(p->is_referenced() || p->is_invalidated());
}
template <typename Config>
void intrusive_ptr_release(intrusive_lru_base<Config> *p) {
/* See object invariants above -- intrusive_ptr_release can only be invoked on
- * referenced objects */
+ * is_referenced() or is_invalidated() objects with live external references */
assert(p);
assert(p->use_count > 0);
- assert(is_referenced());
+ assert(p->is_referenced() || p->is_invalidated());
--p->use_count;
if (p->use_count == 0) {
- p->lru->mark_as_unreferenced(*p);
+ if (p->lru) {
+ p->lru->mark_as_unreferenced(*p);
+ } else {
+ delete p;
+ }
}
}
}
};
+
+static int LIVE_TEST_LRU_ITEMS = 0;
struct TestLRUItem : public ceph::common::intrusive_lru_base<
ceph::common::intrusive_lru_config<
unsigned, TestLRUItem, item_to_unsigned<TestLRUItem>>> {
unsigned key = 0;
int value = 0;
+ bool invalidated = false;
- TestLRUItem(unsigned key) : key(key) {}
+ TestLRUItem(unsigned key) : key(key) {
+ ++LIVE_TEST_LRU_ITEMS;
+ }
+ ~TestLRUItem() { --LIVE_TEST_LRU_ITEMS; }
};
+using TestLRUItemRef = boost::intrusive_ptr<TestLRUItem>;
class LRUTest : public TestLRUItem::lru_t {
public:
ASSERT_FALSE(existed);
}
}
+
+TEST(LRU, clear) {
+ LRUTest cache;
+ const unsigned SIZE = 10;
+ cache.set_target_size(SIZE);
+
+ std::vector<TestLRUItemRef> refs;
+ for (unsigned i = 0; i < 100; ++i) {
+ auto [ref, existed] = cache.add(i, i);
+ ASSERT_FALSE(existed);
+ if ((i % 2) == 0) {
+ refs.push_back(ref);
+ }
+ }
+
+ for (unsigned i = 0; i < 100; i += 2) {
+ auto [ref, existed] = cache.add(i, i);
+ ASSERT_TRUE(existed);
+ }
+
+ cache.clear([](auto &i) { i.invalidated = true; });
+ ASSERT_EQ(refs.size(), LIVE_TEST_LRU_ITEMS);
+
+ for (auto &i: refs) {
+ ASSERT_TRUE(i->invalidated);
+ }
+
+ std::vector<TestLRUItemRef> refs_new;
+ for (unsigned i = 0; i < 100; ++i) {
+ auto [ref, existed] = cache.add(i, i);
+ ASSERT_FALSE(existed);
+ ASSERT_FALSE(ref->invalidated);
+ if ((i % 2) == 0) {
+ refs_new.push_back(ref);
+ }
+ }
+
+ for (unsigned i = 0; i < 100; i += 2) {
+ auto [ref, existed] = cache.add(i, i);
+ ASSERT_TRUE(existed);
+ ASSERT_FALSE(ref->invalidated);
+ }
+
+ refs.clear();
+ cache.set_target_size(0);
+ ASSERT_EQ(refs_new.size(), LIVE_TEST_LRU_ITEMS);
+ cache.set_target_size(SIZE);
+
+ for (unsigned i = 100; i < 200; ++i) {
+ auto [ref, existed] = cache.add(i, i);
+ ASSERT_FALSE(existed);
+ ASSERT_FALSE(ref->invalidated);
+ if ((i % 2) == 0) {
+ refs_new.push_back(ref);
+ }
+ }
+
+ for (unsigned i = 0; i < 200; i += 2) {
+ auto [ref, existed] = cache.add(i, i);
+ ASSERT_TRUE(existed);
+ ASSERT_FALSE(ref->invalidated);
+ }
+
+ ASSERT_EQ(refs_new.size(), LIVE_TEST_LRU_ITEMS);
+ refs_new.clear();
+ ASSERT_EQ(SIZE, LIVE_TEST_LRU_ITEMS);
+ cache.set_target_size(0);
+ ASSERT_EQ(0, LIVE_TEST_LRU_ITEMS);
+}