Skip to content

Commit

Permalink
ObjectCache with intrusive_list specialization (#319)
Browse files Browse the repository at this point in the history
ObjectCache with intrusive_list specialization
  • Loading branch information
Coldwings committed Jan 3, 2024
1 parent 5714b0d commit 0bc243a
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 79 deletions.
6 changes: 3 additions & 3 deletions common/expirecontainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ bool ExpireContainerBase::keep_alive(const Item& x, bool insert_if_not_exists) {
}

ObjectCacheBase::Item* ObjectCacheBase::ref_acquire(const Item& key_item,
Delegate<void*> ctor,
Delegate<void, void*> ctor,
uint64_t failure_cooldown) {
Base::iterator holder;
Item* item = nullptr;
Expand Down Expand Up @@ -106,7 +106,7 @@ ObjectCacheBase::Item* ObjectCacheBase::ref_acquire(const Item& key_item,
SCOPED_LOCK(item->_mtx);
if (!item->_obj && (item->_failure <=
photon::sat_sub(photon::now, failure_cooldown))) {
item->_obj = ctor();
ctor(item);
if (!item->_obj) item->_failure = photon::now;
}
}
Expand Down Expand Up @@ -152,7 +152,7 @@ int ObjectCacheBase::ref_release(ItemPtr item, bool recycle) {
// the argument `key` plays the roles of (type-erased) key
int ObjectCacheBase::release(const ObjectCacheBase::Item& key_item,
bool recycle) {
auto item = find(key_item);
auto item = ExpireContainerBase::TypedIterator<Item>(Base::find(key_item));
if (item == end()) return -1;
return ref_release(*item, recycle);
}
217 changes: 141 additions & 76 deletions common/expirecontainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ limitations under the License.
#include <photon/thread/timer.h>

#include <algorithm>
#include <memory>
#include <tuple>
#include <type_traits>
#include <unordered_set>
Expand Down Expand Up @@ -203,7 +202,7 @@ class ExpireList : public ExpireContainer<T> {
using Base = ExpireContainer<T>;
using Base::Base;
using typename Base::Item;
bool keep_alive(const T& x, bool insert_if_not_exists) {
bool keep_alive(const T &x, bool insert_if_not_exists) {
return Base::keep_alive(Item(x), insert_if_not_exists);
}
};
Expand Down Expand Up @@ -238,12 +237,12 @@ class ObjectCacheBase : public ExpireContainerBase {
// concurrent construction of objects with the same key;
// (2) construction of the object itself, and possibly do
// clean-up in case of failure
Item* ref_acquire(const Item& key_item, Delegate<void*> ctor,
Item* ref_acquire(const Item& key_item, Delegate<void, void*> ctor,
uint64_t failure_cooldown = 0);

int ref_release(ItemPtr item, bool recycle = false);

void* acquire(const Item& key_item, Delegate<void*> ctor,
void* acquire(const Item& key_item, Delegate<void, void*> ctor,
uint64_t failure_cooldown = 0) {
auto ret = ref_acquire(key_item, ctor, failure_cooldown);
return ret ? ret->_obj : nullptr;
Expand All @@ -252,83 +251,78 @@ class ObjectCacheBase : public ExpireContainerBase {
// the argument `key` plays the roles of (type-erased) key
int release(const Item& key_item, bool recycle = false);

using iterator = typename ExpireContainerBase::TypedIterator<Item>;
iterator begin() { return Base::begin(); }
iterator end() { return Base::end(); }
iterator find(const Item& key_item) { return Base::find(key_item); }
};

// Resource pool based on reference count
// when the pool is fulled, it will try to remove items which can be sure is not
// referenced the base m_list works as gc list when object acquired, construct
// or findout the object, add reference count; when object release, reduce
// refcount. if some resource is not referenced, it will be put back to gc list
// waiting to release.
template <typename KeyType, typename ValPtr>
class ObjectCache : public ObjectCacheBase {
protected:
using Base = ObjectCacheBase;
using ValEntity = typename std::remove_pointer<ValPtr>::type;
using KeyedItem = Base::KeyedItem<Base::Item, KeyType>;
class Item : public KeyedItem {
public:
template <typename KeyType, typename ValType>
class PtrItem : public KeyedItem<Item, KeyType> {
public:
using KeyedItem::KeyedItem;
virtual Item* construct() const override {
auto item = new Item(this->_key);
using KeyedItem<Item, KeyType>::KeyedItem;
using typename KeyedItem<Item, KeyType>::InterfaceKey;
using ValPtr = ValType;
using ValEntity = typename std::remove_pointer<ValPtr>::type;
virtual PtrItem* construct() const override {
auto item = new PtrItem(this->_key);
item->_obj = nullptr;
item->_refcnt = 0;
item->_recycle = nullptr;
return item;
}
~Item() override { delete (ValPtr)this->_obj; }
};
~PtrItem() override { delete (ValPtr)this->_obj; }
ValPtr get_ptr() { return (ValPtr)this->_obj; }
ValEntity& get_ref() { return *(ValPtr)this->_obj; }

using ItemKey = typename Item::ItemKey;
using InterfaceKey = typename Item::InterfaceKey;
using ItemPtr = Item*;

public:
ObjectCache(uint64_t expiration) : Base(expiration, expiration / 16) {}
ObjectCache(uint64_t expiration, uint64_t timer_cycle)
: Base(expiration, timer_cycle) {}

template <typename Constructor>
ItemPtr ref_acquire(const InterfaceKey& key, const Constructor& ctor,
uint64_t failure_cooldown = 0) {
auto _ctor = [&]() -> void* { return ctor(); };
// _ctor can always implicit cast to `Delegate<void*>`
return (ItemPtr)Base::ref_acquire(Item(key), _ctor, failure_cooldown);
}
static ValPtr get_content(PtrItem* item) {
return item ? item->get_ptr() : nullptr;
}

int ref_release(ItemPtr item, bool recycle = false) {
return Base::ref_release(item, recycle);
}
static ValPtr create_default() { return new ValEntity(); }
template <typename Ctor>
static decltype(auto) initialize(const Ctor& ctor) {
return [&ctor](void* arg) { ((PtrItem*)arg)->_obj = ctor(); };
}
};

template <typename Constructor>
ValPtr acquire(const InterfaceKey& key, const Constructor& ctor,
uint64_t failure_cooldown = 0) {
auto item = ref_acquire(key, ctor, failure_cooldown);
return (ValPtr)(item ? item->_obj : nullptr);
}
template <typename KeyType, typename ValType>
class ListItem : public KeyedItem<Item, KeyType> {
public:
using ValPtr = ValType*;
using ValEntity = ValType;
ValEntity _list;
using KeyedItem<Item, KeyType>::KeyedItem;
using typename KeyedItem<Item, KeyType>::InterfaceKey;
virtual ListItem* construct() const override {
auto item = new ListItem(this->_key);
item->_obj = nullptr;
item->_refcnt = 0;
item->_recycle = nullptr;
return item;
}
~ListItem() { _list.delete_all(); }
ValPtr get_ptr() { return &this->_list; }
ValEntity& get_ref() { return this->_list; }

int release(const InterfaceKey& key, bool recycle = false) {
return Base::release(Item(key), recycle);
}
static ValEntity& get_content(ListItem* item) {
return item->get_ref();
}

using iterator = typename ExpireContainerBase::TypedIterator<Item>;
iterator begin() { return Base::begin(); }
iterator end() { return Base::end(); }
iterator find(const InterfaceKey& key) {
return Base::find(KeyedItem(key));
}
static ValEntity create_default() { return ValEntity(); }
template <typename Ctor>
static decltype(auto) initialize(const Ctor& ctor) {
return [&ctor](void* arg) {
((ListItem*)arg)->_list = ctor();
((ListItem*)arg)->_obj = arg;
};
}
};

template <typename ObjectCache>
class Borrow {
public:
using Item = typename ObjectCache::Item;
ObjectCache* _oc;
ItemPtr _ref;
Item* _ref;
bool _recycle = false;

public:
Borrow(ObjectCache* oc, ItemPtr ref, bool recycle)
Borrow(ObjectCache* oc, Item* ref, bool recycle)
: _oc(oc), _ref(ref), _recycle(recycle) {}
~Borrow() {
if (_ref) _oc->ref_release(_ref, _recycle);
Expand All @@ -340,19 +334,16 @@ class ObjectCache : public ObjectCacheBase {
void operator=(const Borrow&) = delete;
void operator=(Borrow&& rhs) { move(rhs); }

ValEntity& operator*() { return *get_ptr(); }

ValPtr operator->() { return get_ptr(); }

operator bool() const { return _ref; }

bool recycle() const { return _recycle; }

bool recycle(bool x) { return _recycle = x; }

private:
ValPtr get_ptr() { return (ValPtr)_ref->_obj; }
typename Item::ValPtr operator->() { return _ref->get_ptr(); }
typename Item::ValEntity& operator*() { return _ref->get_ref(); }

protected:
void move(Borrow&& rhs) {
_oc = rhs._oc;
rhs._oc = nullptr;
Expand All @@ -361,14 +352,88 @@ class ObjectCache : public ObjectCacheBase {
_recycle = rhs._recycle;
}
};
};

// Resource pool based on reference count
// when the pool is fulled, it will try to remove items which can be sure is not
// referenced the base m_list works as gc list when object acquired, construct
// or findout the object, add reference count; when object release, reduce
// refcount. if some resource is not referenced, it will be put back to gc list
// waiting to release.
template <typename KeyType, typename ValType, typename ItemType>
class __ObjectCache : public ObjectCacheBase {
public:
using Base = ObjectCacheBase;
using Item = ItemType;
using KeyedItem = Base::KeyedItem<Base::Item, KeyType>;
using InterfaceKey = typename Item::InterfaceKey;
using ItemPtr = Item*;
using ValEntity = typename Item::ValEntity;
using Borrow = typename Base::Borrow<__ObjectCache>;

__ObjectCache(uint64_t expiration) : Base(expiration, expiration / 16) {}
__ObjectCache(uint64_t expiration, uint64_t timer_cycle)
: Base(expiration, timer_cycle) {}

template <typename Constructor>
Borrow borrow(const InterfaceKey& key, const Constructor& ctor,
uint64_t failure_cooldown = 0) {
return Borrow(this, ref_acquire(key, ctor, failure_cooldown), false);
ItemPtr ref_acquire(const InterfaceKey& key, const Constructor& ctor,
uint64_t failure_cooldown = 0) {
auto _ctor = Item::initialize(ctor);
return (ItemPtr)Base::ref_acquire(Item(key), _ctor, failure_cooldown);
}

template <typename Constructor>
decltype(auto) acquire(const InterfaceKey& key, const Constructor& ctor,
uint64_t failure_cooldown = 0) {
return Item::get_content(ref_acquire(key, ctor, failure_cooldown));
}

Borrow borrow(const InterfaceKey& key) {
return borrow(key, [] { return new ValEntity(); });
int ref_release(ItemPtr item, bool recycle = false) {
return Base::ref_release(item, recycle);
}

int release(const InterfaceKey& key, bool recycle = false) {
return Base::release(Item(key), recycle);
}

using iterator = typename ExpireContainerBase::TypedIterator<Item>;
iterator begin() { return Base::begin(); }
iterator end() { return Base::end(); }
iterator find(const InterfaceKey& key) {
return Base::find(KeyedItem(key));
}

template <typename Constructor>
Borrow borrow(const typename Item::InterfaceKey& key,
const Constructor& ctor, uint64_t failure_cooldown = 0) {
return Borrow(
this,
((__ObjectCache*)this)->ref_acquire(key, ctor, failure_cooldown),
false);
}

Borrow borrow(const typename Item::InterfaceKey& key) {
return borrow(key, &Item::create_default);
}
};

template <typename KeyType, typename ValPtr>
class ObjectCache
: public __ObjectCache<KeyType, ValPtr,
ObjectCacheBase::PtrItem<KeyType, ValPtr>> {
public:
using __ObjectCache<
KeyType, ValPtr,
ObjectCacheBase::PtrItem<KeyType, ValPtr>>::__ObjectCache;
};

template <typename KeyType, typename NodeType>
class ObjectCache<KeyType, intrusive_list<NodeType>>
: public __ObjectCache<
KeyType, intrusive_list<NodeType>,
ObjectCacheBase::ListItem<KeyType, intrusive_list<NodeType>>> {
public:
using __ObjectCache<KeyType, intrusive_list<NodeType>,
ObjectCacheBase::ListItem<
KeyType, intrusive_list<NodeType>>>::__ObjectCache;
};
34 changes: 34 additions & 0 deletions common/test/test_objcache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,40 @@ TEST(ExpireList, expire_container) {
EXPECT_EQ(expire.end(), it);
}

struct simple_node : intrusive_list_node<simple_node> {
int id;

simple_node(int x):id(x) {}
};

struct OCArgL {
ObjectCache<int, intrusive_list<simple_node>>* oc;
int id;
};

TEST(ObjCache, with_list) {
set_log_output_level(ALOG_INFO);
DEFER(set_log_output_level(ALOG_DEBUG));
ObjectCache<int, intrusive_list<simple_node>> ocache(1000UL * 1000 * 10);
for (int i=0;i<10;i++) {
auto &list = ocache.acquire(0, []()->intrusive_list<simple_node> {return {};});
list.push_back(new simple_node(i));
}
for (int i=0;i<10;i++) {
ocache.release(0);
}
{
int cnt = 0;
for (;;) {
auto b = ocache.borrow(0);
LOG_INFO(VALUE(b->pop_front()->id));
cnt ++;
if (b->empty()) break;
}
EXPECT_EQ(10, cnt);
}
}

int main(int argc, char** argv) {
photon::vcpu_init();
DEFER(photon::vcpu_fini());
Expand Down

0 comments on commit 0bc243a

Please sign in to comment.