From 3ce00a5ebef41480c9f6ab30f515f8bc17c53572 Mon Sep 17 00:00:00 2001 From: Zygo Blaxell Date: Mon, 19 Oct 2020 12:43:19 -0400 Subject: [PATCH] lib: introduce Pool, a class for storing reusable anonymous objects Pool is a place to store shared_ptrs to generated objects (T) that are too expensive to create and destroy between individual uses, such as temporary files. Objects in a Pool have no distinct identity (contrast with Cache or NamedPtr). Users of the Pool invoke the Pool function call overload and "check out" a shared_ptr for a T object from the Pool. When the last referencing shared_otr is destroyed, the T object is "checked in" to the Pool. Each call of the Pool function overload checks out a shared_ptr to a T object that is not currently referenced by any other public shared_ptr. If there are no existing T objects in the Pool, a new T is constructed by calling the generator function. The clear() method destroys all checked in T objects owned by the Pool at the time the method is called. T objects that are checked out are not affected by clear(), and they will be stored in the Pool when they are checked in. If the checkout function is provided, it is called on a shared_ptr during checkout, before returning to the caller. If the checkin function is provided, it is called on a shared_ptr before returning it to the Pool. The checkin function must not throw exceptions. The Pool may be destroyed while T objects are checked out of the Pool. In that case, when the T objects are checked in, the T object is immediately destroyed without calling the checkin function. Signed-off-by: Zygo Blaxell --- include/crucible/pool.h | 185 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 include/crucible/pool.h diff --git a/include/crucible/pool.h b/include/crucible/pool.h new file mode 100644 index 0000000..ccb634b --- /dev/null +++ b/include/crucible/pool.h @@ -0,0 +1,185 @@ +#ifndef CRUCIBLE_POOL_H +#define CRUCIBLE_POOL_H + +#include +#include +#include +#include + +namespace crucible { + using namespace std; + + /// Storage for reusable anonymous objects that are too expensive to create and/or destroy frequently + + template + class Pool { + public: + using Ptr = shared_ptr; + using Generator = function; + using Checker = function; + + ~Pool(); + Pool(Generator f = Generator(), Checker checkin = Checker(), Checker checkout = Checker()); + + /// Function to create new objects when Pool is empty + void generator(Generator f); + + /// Optional function called when objects exit the pool (user handle is created and returned to user) + void checkout(Checker f); + + /// Optional function called when objects enter the pool (last user handle is destroyed) + void checkin(Checker f); + + /// Pool() returns a handle to an object of type shared_ptr + Ptr operator()(); + + /// Destroy all objects in Pool that are not in use + void clear(); + + private: + using ListType = list; + struct ListRep { + ListType m_list; + mutex m_mutex; + bool m_destroyed = false; + Checker m_checkin; + ListRep(Checker checkin); + }; + struct Handle { + shared_ptr m_list_rep; + Ptr m_ret_ptr; + Handle(shared_ptr list_rep, Ptr ret_ptr); + ~Handle(); + }; + + Generator m_fn; + Checker m_checkout; + shared_ptr m_list_rep; + }; + + template + Pool::ListRep::ListRep(Checker checkin) : + m_checkin(checkin) + { + } + + template + Pool::Pool(Generator f, Checker checkin, Checker checkout) : + m_fn(f), + m_checkout(checkout), + m_list_rep(make_shared(checkin)) + { + } + + template + Pool::~Pool() + { + unique_lock lock(m_list_rep->m_mutex); + m_list_rep->m_destroyed = true; + m_list_rep->m_list.clear(); + } + + template + Pool::Handle::Handle(shared_ptr list_rep, Ptr ret_ptr) : + m_list_rep(list_rep), + m_ret_ptr(ret_ptr) + { + } + + template + Pool::Handle::~Handle() + { + unique_lock lock(m_list_rep->m_mutex); + + // Checkin prepares the object for storage and reuse. + // Neither of those will happen if there is no Pool. + // If the Pool was destroyed, just let m_ret_ptr expire. + if (!m_list_rep->m_destroyed) { + + // If a checkin function is defined, call it + auto checkin = m_list_rep->m_checkin; + if (checkin) { + lock.unlock(); + checkin(m_ret_ptr); + lock.lock(); + } + + // Place object back in pool + m_list_rep->m_list.push_front(m_ret_ptr); + + } + } + + template + typename Pool::Ptr + Pool::operator()() + { + Ptr rv; + + // Do we have an object in the pool we can return instead? + unique_lock lock(m_list_rep->m_mutex); + if (m_list_rep->m_list.empty()) { + // No, release cache lock and call the function + lock.unlock(); + + // Create new value + rv = m_fn(); + } else { + rv = m_list_rep->m_list.front(); + m_list_rep->m_list.pop_front(); + + // Release lock so we don't deadlock with Handle destructor + lock.unlock(); + } + + // rv now points to a new T object that is not in the list. + THROW_CHECK0(runtime_error, rv); + + // Construct a shared_ptr for Handle which will refcount the Handle objects + // and reinsert the T into the Pool when the last Handle is destroyed. + auto hv = make_shared(m_list_rep, rv); + + // If a checkout function is defined, call it + if (m_checkout) { + m_checkout(rv); + } + + // T an alias shared_ptr for the T using Handle's refcount. + return Ptr(hv, rv.get()); + } + + template + void + Pool::generator(Generator func) + { + unique_lock lock(m_list_rep->m_mutex); + m_fn = func; + } + + template + void + Pool::checkin(Checker func) + { + unique_lock lock(m_list_rep->m_mutex); + m_list_rep->m_checkin = func; + } + + template + void + Pool::checkout(Checker func) + { + unique_lock lock(m_list_rep->m_mutex); + m_checkout = func; + } + + template + void + Pool::clear() + { + unique_lock lock(m_list_rep->m_mutex); + m_list_rep->m_list.clear(); + } + +} + +#endif // POOL_H