From 0bc243ab8db382f12b629e0aca34433a89f38d45 Mon Sep 17 00:00:00 2001 From: Coldwings Date: Wed, 3 Jan 2024 09:53:04 +0800 Subject: [PATCH] ObjectCache with intrusive_list specialization (#319) ObjectCache with intrusive_list specialization --- common/expirecontainer.cpp | 6 +- common/expirecontainer.h | 217 ++++++++++++++++++++++------------ common/test/test_objcache.cpp | 34 ++++++ 3 files changed, 178 insertions(+), 79 deletions(-) diff --git a/common/expirecontainer.cpp b/common/expirecontainer.cpp index 2d24f0a8..6bf68df9 100644 --- a/common/expirecontainer.cpp +++ b/common/expirecontainer.cpp @@ -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 ctor, + Delegate ctor, uint64_t failure_cooldown) { Base::iterator holder; Item* item = nullptr; @@ -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; } } @@ -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(Base::find(key_item)); if (item == end()) return -1; return ref_release(*item, recycle); } diff --git a/common/expirecontainer.h b/common/expirecontainer.h index 6f90fe2a..c34b01c9 100644 --- a/common/expirecontainer.h +++ b/common/expirecontainer.h @@ -24,7 +24,6 @@ limitations under the License. #include #include -#include #include #include #include @@ -203,7 +202,7 @@ class ExpireList : public ExpireContainer { using Base = ExpireContainer; 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); } }; @@ -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 ctor, + Item* ref_acquire(const Item& key_item, Delegate ctor, uint64_t failure_cooldown = 0); int ref_release(ItemPtr item, bool recycle = false); - void* acquire(const Item& key_item, Delegate ctor, + void* acquire(const Item& key_item, Delegate ctor, uint64_t failure_cooldown = 0) { auto ret = ref_acquire(key_item, ctor, failure_cooldown); return ret ? ret->_obj : nullptr; @@ -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; - 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 -class ObjectCache : public ObjectCacheBase { -protected: - using Base = ObjectCacheBase; - using ValEntity = typename std::remove_pointer::type; - using KeyedItem = Base::KeyedItem; - class Item : public KeyedItem { +public: + template + class PtrItem : public KeyedItem { public: - using KeyedItem::KeyedItem; - virtual Item* construct() const override { - auto item = new Item(this->_key); + using KeyedItem::KeyedItem; + using typename KeyedItem::InterfaceKey; + using ValPtr = ValType; + using ValEntity = typename std::remove_pointer::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 - 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` - 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 + static decltype(auto) initialize(const Ctor& ctor) { + return [&ctor](void* arg) { ((PtrItem*)arg)->_obj = ctor(); }; + } + }; - template - 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 + class ListItem : public KeyedItem { + public: + using ValPtr = ValType*; + using ValEntity = ValType; + ValEntity _list; + using KeyedItem::KeyedItem; + using typename KeyedItem::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; - 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 + static decltype(auto) initialize(const Ctor& ctor) { + return [&ctor](void* arg) { + ((ListItem*)arg)->_list = ctor(); + ((ListItem*)arg)->_obj = arg; + }; + } + }; + template 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); @@ -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; @@ -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 +class __ObjectCache : public ObjectCacheBase { +public: + using Base = ObjectCacheBase; + using Item = ItemType; + using KeyedItem = Base::KeyedItem; + 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 - 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 + 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; + iterator begin() { return Base::begin(); } + iterator end() { return Base::end(); } + iterator find(const InterfaceKey& key) { + return Base::find(KeyedItem(key)); + } + + template + 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 +class ObjectCache + : public __ObjectCache> { +public: + using __ObjectCache< + KeyType, ValPtr, + ObjectCacheBase::PtrItem>::__ObjectCache; +}; + +template +class ObjectCache> + : public __ObjectCache< + KeyType, intrusive_list, + ObjectCacheBase::ListItem>> { +public: + using __ObjectCache, + ObjectCacheBase::ListItem< + KeyType, intrusive_list>>::__ObjectCache; }; diff --git a/common/test/test_objcache.cpp b/common/test/test_objcache.cpp index 0c90cf82..78f25842 100644 --- a/common/test/test_objcache.cpp +++ b/common/test/test_objcache.cpp @@ -358,6 +358,40 @@ TEST(ExpireList, expire_container) { EXPECT_EQ(expire.end(), it); } +struct simple_node : intrusive_list_node { + int id; + + simple_node(int x):id(x) {} +}; + +struct OCArgL { + ObjectCache>* oc; + int id; +}; + +TEST(ObjCache, with_list) { + set_log_output_level(ALOG_INFO); + DEFER(set_log_output_level(ALOG_DEBUG)); + ObjectCache> ocache(1000UL * 1000 * 10); + for (int i=0;i<10;i++) { + auto &list = ocache.acquire(0, []()->intrusive_list {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());