mirror of
https://github.com/Zygo/bees.git
synced 2025-05-17 13:25:45 +02:00
Drop the ListType alias because we only use it once. Rename ListRep to PoolRep to better reflect what it does. We don't need the Pool to be available to handle destroyed Pool::Handle objects. A weak_ptr in the Handle would detect the Pool has been destroyed, so we don't need to track that ourselves. As a bonus, we can destroy the PoolRep object as soon as the Pool has been destroyed, delayed only if there is a Handle object currently executing its destructor. Signed-off-by: Zygo Blaxell <bees@furryterror.org>
184 lines
4.0 KiB
C++
184 lines
4.0 KiB
C++
#ifndef CRUCIBLE_POOL_H
|
|
#define CRUCIBLE_POOL_H
|
|
|
|
#include <functional>
|
|
#include <list>
|
|
#include <memory>
|
|
#include <mutex>
|
|
|
|
namespace crucible {
|
|
using namespace std;
|
|
|
|
/// Storage for reusable anonymous objects that are too expensive to create and/or destroy frequently
|
|
|
|
template <class T>
|
|
class Pool {
|
|
public:
|
|
using Ptr = shared_ptr<T>;
|
|
using Generator = function<Ptr()>;
|
|
using Checker = function<void(Ptr)>;
|
|
|
|
~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<T>
|
|
Ptr operator()();
|
|
|
|
/// Destroy all objects in Pool that are not in use
|
|
void clear();
|
|
|
|
private:
|
|
struct PoolRep {
|
|
list<Ptr> m_list;
|
|
mutex m_mutex;
|
|
Checker m_checkin;
|
|
PoolRep(Checker checkin);
|
|
};
|
|
struct Handle {
|
|
weak_ptr<PoolRep> m_list_rep;
|
|
Ptr m_ret_ptr;
|
|
Handle(shared_ptr<PoolRep> list_rep, Ptr ret_ptr);
|
|
~Handle();
|
|
};
|
|
|
|
Generator m_fn;
|
|
Checker m_checkout;
|
|
shared_ptr<PoolRep> m_list_rep;
|
|
};
|
|
|
|
template <class T>
|
|
Pool<T>::PoolRep::PoolRep(Checker checkin) :
|
|
m_checkin(checkin)
|
|
{
|
|
}
|
|
|
|
template <class T>
|
|
Pool<T>::Pool(Generator f, Checker checkin, Checker checkout) :
|
|
m_fn(f),
|
|
m_checkout(checkout),
|
|
m_list_rep(make_shared<PoolRep>(checkin))
|
|
{
|
|
}
|
|
|
|
template <class T>
|
|
Pool<T>::~Pool()
|
|
{
|
|
auto list_rep = m_list_rep;
|
|
unique_lock<mutex> lock(list_rep->m_mutex);
|
|
m_list_rep.reset();
|
|
}
|
|
|
|
template <class T>
|
|
Pool<T>::Handle::Handle(shared_ptr<PoolRep> list_rep, Ptr ret_ptr) :
|
|
m_list_rep(list_rep),
|
|
m_ret_ptr(ret_ptr)
|
|
{
|
|
}
|
|
|
|
template <class T>
|
|
Pool<T>::Handle::~Handle()
|
|
{
|
|
// 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.
|
|
auto list_rep = m_list_rep.lock();
|
|
if (!list_rep) {
|
|
return;
|
|
}
|
|
|
|
unique_lock<mutex> lock(list_rep->m_mutex);
|
|
// If a checkin function is defined, call it
|
|
auto checkin = list_rep->m_checkin;
|
|
if (checkin) {
|
|
lock.unlock();
|
|
checkin(m_ret_ptr);
|
|
lock.lock();
|
|
}
|
|
|
|
// Place object back in pool
|
|
list_rep->m_list.push_front(m_ret_ptr);
|
|
}
|
|
|
|
template <class T>
|
|
typename Pool<T>::Ptr
|
|
Pool<T>::operator()()
|
|
{
|
|
Ptr rv;
|
|
|
|
// Do we have an object in the pool we can return instead?
|
|
unique_lock<mutex> 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 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<Handle>(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 <class T>
|
|
void
|
|
Pool<T>::generator(Generator func)
|
|
{
|
|
unique_lock<mutex> lock(m_list_rep->m_mutex);
|
|
m_fn = func;
|
|
}
|
|
|
|
template <class T>
|
|
void
|
|
Pool<T>::checkin(Checker func)
|
|
{
|
|
unique_lock<mutex> lock(m_list_rep->m_mutex);
|
|
m_list_rep->m_checkin = func;
|
|
}
|
|
|
|
template <class T>
|
|
void
|
|
Pool<T>::checkout(Checker func)
|
|
{
|
|
unique_lock<mutex> lock(m_list_rep->m_mutex);
|
|
m_checkout = func;
|
|
}
|
|
|
|
template <class T>
|
|
void
|
|
Pool<T>::clear()
|
|
{
|
|
unique_lock<mutex> lock(m_list_rep->m_mutex);
|
|
m_list_rep->m_list.clear();
|
|
}
|
|
|
|
}
|
|
|
|
#endif // POOL_H
|