From 66a29f972bf062b4345bac56a8161032638580e0 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Tue, 17 Nov 2015 22:24:56 +0000 Subject: Add a template resource pool with limits and caching support. --- libadhocutil/resourcePool.cpp | 28 ++++ libadhocutil/resourcePool.h | 126 +++++++++++++++ libadhocutil/resourcePool.impl.h | 238 +++++++++++++++++++++++++++ libadhocutil/unittests/Jamfile.jam | 12 ++ libadhocutil/unittests/testResourcePool.cpp | 242 ++++++++++++++++++++++++++++ 5 files changed, 646 insertions(+) create mode 100644 libadhocutil/resourcePool.cpp create mode 100644 libadhocutil/resourcePool.h create mode 100644 libadhocutil/resourcePool.impl.h create mode 100644 libadhocutil/unittests/testResourcePool.cpp diff --git a/libadhocutil/resourcePool.cpp b/libadhocutil/resourcePool.cpp new file mode 100644 index 0000000..29e6cb9 --- /dev/null +++ b/libadhocutil/resourcePool.cpp @@ -0,0 +1,28 @@ +#include "resourcePool.h" +#include "buffer.h" + +namespace AdHoc { + TimeOutOnResourcePool::TimeOutOnResourcePool(const char * const n) : + name(n) + { + } + + std::string + TimeOutOnResourcePool::message() const throw() + { + return stringbf("Timeout getting a resource from pool of %s", name); + } + + NoCurrentResource::NoCurrentResource(const std::thread::id & id, const char * const n) : + threadId(id), + name(n) + { + } + + std::string + NoCurrentResource::message() const throw() + { + return stringbf("Thread %s has no current resource handle of type %s", threadId, name); + } +} + diff --git a/libadhocutil/resourcePool.h b/libadhocutil/resourcePool.h new file mode 100644 index 0000000..70d9ae2 --- /dev/null +++ b/libadhocutil/resourcePool.h @@ -0,0 +1,126 @@ +#ifndef ADHOCUTIL_RESOURCEPOOL_H +#define ADHOCUTIL_RESOURCEPOOL_H + +#include +#include +#include +#include +#include +#include +#include "semaphore.h" +#include "exception.h" +#include "visibility.h" + +namespace AdHoc { + template + class ResourcePool; + + /// A handle to a resource allocated from a ResourcePool. + template + class DLL_PUBLIC ResourceHandle { + public: + /// Handle to an allocated resource, the pool it belongs to and a count of active references. + typedef boost::tuple *, std::atomic> Object; + + /// Create a reference to a new resource. + ResourceHandle(Object *); + ResourceHandle(const ResourceHandle &); + ~ResourceHandle(); + + void operator=(const ResourceHandle &); + Resource * operator->() const; + Resource * get() const; + + unsigned int handleCount() const; + + private: + DLL_PRIVATE void incRef() const; + DLL_PRIVATE void decRef(); + Object * resource; + }; + + /// A fully featured resource pool for sharing and reusing a finite set of + /// resources, possibly across multiple threads. + template + class DLL_PUBLIC ResourcePool { + public: + friend class ResourceHandle; + + ResourcePool(unsigned int maxSize, unsigned int keep); + virtual ~ResourcePool(); + + ResourceHandle get(); + ResourceHandle get(unsigned int); + ResourceHandle getMine(); + void idle(); + + unsigned int inUseCount() const; + unsigned int availableCount() const; + + protected: + virtual Resource * createResource() const = 0; + virtual void destroyResource(Resource *) const; + virtual void testResource(const Resource *) const; + + private: + typedef std::list Available; + typedef std::multimap::Object *> InUse; + + void putBack(Resource *); + void discard(Resource *); + + DLL_PRIVATE static void removeFrom(Resource *, InUse &); + DLL_PRIVATE ResourceHandle getOne(); + + mutable boost::upgrade_mutex lock; + Semaphore poolSize; + unsigned int keep; + Available available; + InUse inUse; + }; + + /// Represents a failure to acquire a new resource within the given timeout. + class DLL_PUBLIC TimeOutOnResourcePool : public AdHoc::StdException { + public: + TimeOutOnResourcePool(const char * const type); + + std::string message() const throw() override; + + private: + const char * const name; + }; + + /// Represents a failure to acquire a new resource of type R within the given timeout. + template + class DLL_PUBLIC TimeOutOnResourcePoolT : public TimeOutOnResourcePool { + public: + TimeOutOnResourcePoolT(); + }; + + /// Represents a request for the current thread's previous allocated resource + /// when one has not been allocated. + class DLL_PUBLIC NoCurrentResource : public AdHoc::StdException { + public: + /// Construct for a specific thread and resource type. + NoCurrentResource(const std::thread::id &, const char * const type); + + std::string message() const throw() override; + + private: + const std::thread::id threadId; + const char * const name; + }; + + /// Represents a request for the current thread's previous allocated resource + /// of type R when one has not been allocated. + template + class DLL_PUBLIC NoCurrentResourceT : public NoCurrentResource { + public: + /// Construct for a specific thread and resource type R. + NoCurrentResourceT(const std::thread::id &); + }; + +} + +#endif + diff --git a/libadhocutil/resourcePool.impl.h b/libadhocutil/resourcePool.impl.h new file mode 100644 index 0000000..d4fcfe4 --- /dev/null +++ b/libadhocutil/resourcePool.impl.h @@ -0,0 +1,238 @@ +#ifndef ADHOCUTIL_RESOURCEPOOL_IMPL_H +#define ADHOCUTIL_RESOURCEPOOL_IMPL_H + +#include "resourcePool.h" +#include "lockHelpers.h" +#include "safeMapFind.h" + +namespace AdHoc { + // + // ResourceHandle + // + + template + ResourceHandle::ResourceHandle(Object * o) : + resource(o) + { + incRef(); + } + + template + ResourceHandle::ResourceHandle(const ResourceHandle & rh) : + resource(rh.resource) + { + incRef(); + } + + template + ResourceHandle::~ResourceHandle() + { + decRef(); + } + + template + unsigned int + ResourceHandle::handleCount() const + { + return boost::get<2>(*resource); + } + + template + R * + ResourceHandle::get() const + { + return boost::get<0>(*resource); + } + + template + R * + ResourceHandle::operator->() const + { + return boost::get<0>(*resource); + } + + template + void + ResourceHandle::incRef() const + { + ++boost::get<2>(*resource); + } + + template + void + ResourceHandle::decRef() + { + if (!--boost::get<2>(*resource)) { + if (std::uncaught_exception()) { + boost::get<1>(*resource)->discard(boost::get<0>(*resource)); + } + else { + boost::get<1>(*resource)->putBack(boost::get<0>(*resource)); + } + delete resource; + } + resource = nullptr; + } + + // + // ResourcePool + // + + template + ResourcePool::ResourcePool(unsigned int max, unsigned int k) : + poolSize(max), + keep(k) + { + } + + template + ResourcePool::~ResourcePool() + { + for (auto & r : available) { + destroyResource(r); + } + for (auto & r : inUse) { + destroyResource(boost::get<0>(*r.second)); + } + } + + template + void + ResourcePool::destroyResource(R * r) const + { + delete r; + } + + template + void + ResourcePool::testResource(const R *) const + { + } + + template + unsigned int + ResourcePool::inUseCount() const + { + SharedLock(lock); + return inUse.size(); + } + + template + unsigned int + ResourcePool::availableCount() const + { + SharedLock(lock); + return available.size(); + } + + template + ResourceHandle + ResourcePool::getMine() + { + Lock(lock); + return safeMapLookup>(inUse, std::this_thread::get_id()); + } + + template + void + ResourcePool::idle() + { + Lock(lock); + for (auto & r : available) { + destroyResource(r); + } + available.clear(); + } + + template + ResourceHandle + ResourcePool::get() + { + poolSize.wait(); + return getOne(); + } + + template + ResourceHandle + ResourcePool::get(unsigned int timeout) + { + if (!poolSize.wait(timeout)) { + throw TimeOutOnResourcePoolT(); + } + return getOne(); + } + + template + ResourceHandle + ResourcePool::getOne() + { + UpgradableLock(lock, ulock); + if (available.empty()) { + auto ro = new typename ResourceHandle::Object(createResource(), this); + UpgradeLock(ulock); + inUse.insert({ std::this_thread::get_id(), ro }); + return ro; + } + else { + UpgradeLock(ulock); + auto ro = new typename ResourceHandle::Object(available.front(), this); + available.pop_front(); + inUse.insert({ std::this_thread::get_id(), ro }); + return ro; + } + } + + template + void + ResourcePool::putBack(R * r) + { + Lock(lock); + removeFrom(r, inUse); + if (available.size() < keep) { + available.push_back(r); + } + else { + destroyResource(r); + } + poolSize.notify(); + } + + template + void + ResourcePool::discard(R * r) + { + Lock(lock); + removeFrom(r, inUse); + destroyResource(r); + poolSize.notify(); + } + + template + void + ResourcePool::removeFrom(R * r, InUse & inUse) + { + auto rs = inUse.equal_range(std::this_thread::get_id()); + for (auto & ri = rs.first; ri != rs.second; ri++) { + if (boost::get<0>(*ri->second) == r) { + inUse.erase(ri); + return; + } + } + } + + template + TimeOutOnResourcePoolT::TimeOutOnResourcePoolT() : + TimeOutOnResourcePool(typeid(R).name()) + { + } + + template + NoCurrentResourceT::NoCurrentResourceT(const std::thread::id & id) : + NoCurrentResource(id, typeid(R).name()) + { + } + +} + +#endif + diff --git a/libadhocutil/unittests/Jamfile.jam b/libadhocutil/unittests/Jamfile.jam index 334b47e..582b174 100644 --- a/libadhocutil/unittests/Jamfile.jam +++ b/libadhocutil/unittests/Jamfile.jam @@ -185,3 +185,15 @@ run testException ; +run + testResourcePool.cpp + : : : + BOOST_TEST_DYN_LINK + ..//adhocutil + boost_utf + boost_thread + boost_system + : + testResourcePool + ; + diff --git a/libadhocutil/unittests/testResourcePool.cpp b/libadhocutil/unittests/testResourcePool.cpp new file mode 100644 index 0000000..8b4161e --- /dev/null +++ b/libadhocutil/unittests/testResourcePool.cpp @@ -0,0 +1,242 @@ +#define BOOST_TEST_MODULE ResourcePool +#include + +#include + +class MockResource { + public: + MockResource() { count += 1; } + ~MockResource() { count -= 1; } + + MockResource(const MockResource &) = delete; + void operator=(const MockResource &) = delete; + + static std::atomic count; +}; + +std::atomic MockResource::count; + +class TRP : public AdHoc::ResourcePool { + public: + TRP() : AdHoc::ResourcePool(10, 10) { } + protected: + MockResource * createResource() const override + { + return new MockResource(); + } +}; + +class TRPSmall : public AdHoc::ResourcePool { + public: + TRPSmall() : AdHoc::ResourcePool(3, 1) { } + protected: + MockResource * createResource() const override + { + return new MockResource(); + } +}; + +BOOST_AUTO_TEST_CASE ( get ) +{ + { + TRP pool; + BOOST_REQUIRE_EQUAL(0, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(0, pool.availableCount()); + + { + auto r1 = pool.get(); + BOOST_REQUIRE_EQUAL(1, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(0, pool.availableCount()); + BOOST_REQUIRE_EQUAL(1, r1.handleCount()); + BOOST_REQUIRE_EQUAL(1, MockResource::count); + BOOST_REQUIRE(r1.get()); + + auto r2(pool.get()); + BOOST_REQUIRE_EQUAL(2, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(0, pool.availableCount()); + BOOST_REQUIRE_EQUAL(1, r2.handleCount()); + BOOST_REQUIRE_EQUAL(2, MockResource::count); + BOOST_REQUIRE(r2.get()); + + auto r1a = r1; + BOOST_REQUIRE_EQUAL(2, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(0, pool.availableCount()); + BOOST_REQUIRE_EQUAL(2, r1.handleCount()); + BOOST_REQUIRE_EQUAL(2, r1a.handleCount()); + BOOST_REQUIRE_EQUAL(2, MockResource::count); + BOOST_REQUIRE(r1.get()); + BOOST_REQUIRE(r1a.get()); + BOOST_REQUIRE_EQUAL(r1.get(), r1a.get()); + } + + BOOST_REQUIRE_EQUAL(0, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(2, pool.availableCount()); + } + BOOST_REQUIRE_EQUAL(0, MockResource::count); +} + +BOOST_AUTO_TEST_CASE ( getMine ) +{ + TRP pool; + auto r1 = pool.get(); + BOOST_REQUIRE(r1.get()); + auto r2 = pool.getMine(); + BOOST_REQUIRE_EQUAL(r1.get(), r2.get()); + BOOST_REQUIRE_EQUAL(2, r1.handleCount()); + BOOST_REQUIRE_EQUAL(2, r2.handleCount()); +} + +BOOST_AUTO_TEST_CASE( getMineNoCurrent ) +{ + TRP pool; + BOOST_REQUIRE_THROW(pool.getMine(), AdHoc::NoCurrentResourceT); + { + auto r1 = pool.get(); + auto r2 = pool.getMine(); + BOOST_REQUIRE_EQUAL(r1.get(), r2.get()); + } + BOOST_REQUIRE_THROW(pool.getMine(), AdHoc::NoCurrentResource); +} + +BOOST_AUTO_TEST_CASE( discard ) +{ + TRP pool; + try { + auto r1 = pool.get(); + BOOST_REQUIRE_EQUAL(0, pool.availableCount()); + BOOST_REQUIRE_EQUAL(1, pool.inUseCount()); + throw std::exception(); + } + catch (...) { + BOOST_REQUIRE_EQUAL(0, pool.availableCount()); + BOOST_REQUIRE_EQUAL(0, pool.inUseCount()); + } +} + +BOOST_AUTO_TEST_CASE( keepSome1 ) +{ + TRPSmall pool; + { + auto r1 = pool.get(); + { + auto r2 = pool.get(); + { + auto r3 = pool.get(); + BOOST_REQUIRE_EQUAL(3, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(0, pool.availableCount()); + BOOST_REQUIRE_EQUAL(3, MockResource::count); + } + BOOST_REQUIRE_EQUAL(2, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(1, pool.availableCount()); + BOOST_REQUIRE_EQUAL(3, MockResource::count); + } + BOOST_REQUIRE_EQUAL(1, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(1, pool.availableCount()); + BOOST_REQUIRE_EQUAL(2, MockResource::count); + { + auto r2 = pool.get(); + BOOST_REQUIRE_EQUAL(2, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(0, pool.availableCount()); + BOOST_REQUIRE_EQUAL(2, MockResource::count); + } + BOOST_REQUIRE_EQUAL(1, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(1, pool.availableCount()); + BOOST_REQUIRE_EQUAL(2, MockResource::count); + } + BOOST_REQUIRE_EQUAL(0, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(1, pool.availableCount()); + BOOST_REQUIRE_EQUAL(1, MockResource::count); +} + +BOOST_AUTO_TEST_CASE( keepSome2 ) +{ + TRPSmall pool; + { + auto r1 = pool.get(); + auto r2 = pool.get(); + auto r3 = pool.get(); + BOOST_REQUIRE_EQUAL(3, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(0, pool.availableCount()); + BOOST_REQUIRE_EQUAL(3, MockResource::count); + } + BOOST_REQUIRE_EQUAL(0, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(1, pool.availableCount()); + BOOST_REQUIRE_EQUAL(1, MockResource::count); +} + +BOOST_AUTO_TEST_CASE( idle ) +{ + TRP pool; + { + { + auto r1 = pool.get(); + auto r2 = pool.get(); + } + auto r3 = pool.get(); + BOOST_REQUIRE_EQUAL(1, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(1, pool.availableCount()); + BOOST_REQUIRE_EQUAL(2, MockResource::count); + pool.idle(); + BOOST_REQUIRE_EQUAL(1, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(0, pool.availableCount()); + BOOST_REQUIRE_EQUAL(1, MockResource::count); + } + BOOST_REQUIRE_EQUAL(0, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(1, pool.availableCount()); + BOOST_REQUIRE_EQUAL(1, MockResource::count); + pool.idle(); + BOOST_REQUIRE_EQUAL(0, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(0, pool.availableCount()); + BOOST_REQUIRE_EQUAL(0, MockResource::count); +} + +BOOST_AUTO_TEST_CASE( threading1 ) +{ + TRPSmall pool; + std::list threads; + for (int x = 0; x < 100; x += 1) { + threads.push_back(new std::thread([&pool](){ + auto r = pool.get(); + usleep(50000); + })); + usleep(5000); + // pool size never exceeds 3 + BOOST_REQUIRE(pool.inUseCount() <= 3); + } + for(std::thread * thread : threads) { + thread->join(); + delete thread; + } + // pool keep returns to 1 + BOOST_REQUIRE_EQUAL(1, pool.availableCount()); +} + +static +void +acquireAndKeepFor1Second(TRPSmall * pool) +{ + auto r = pool->get(); + sleep(1); +} + +BOOST_AUTO_TEST_CASE( threading2 ) +{ + TRPSmall pool; + std::thread t1([&pool]() { acquireAndKeepFor1Second(&pool); }); + std::thread t2([&pool]() { acquireAndKeepFor1Second(&pool); }); + std::thread t3([&pool]() { acquireAndKeepFor1Second(&pool); }); + + BOOST_REQUIRE_THROW(pool.get(100), AdHoc::TimeOutOnResourcePoolT); + BOOST_REQUIRE_EQUAL(3, pool.inUseCount()); + + t1.join(); + { + auto r = pool.get(0); + t2.join(); + t3.join(); + } + + BOOST_REQUIRE_EQUAL(0, pool.inUseCount()); + BOOST_REQUIRE_EQUAL(1, pool.availableCount()); +} + -- cgit v1.2.3