summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Conway <aconway@apache.org>2008-02-19 22:58:41 +0000
committerAlan Conway <aconway@apache.org>2008-02-19 22:58:41 +0000
commit2284b517e15c7397d1271cb948fb1d24fce24841 (patch)
tree47fd35abcd32d36b2203719c8afb4a8bcd1850d1
parentf9631361fc24787ebe785060b9554a92b90a60d3 (diff)
downloadqpid-python-2284b517e15c7397d1271cb948fb1d24fce24841.tar.gz
sys::RefCountedMap - reference-counted weak map of reference-counted objects.
Ensures objects are atomically deleted and removed from the map. git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid@629263 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--cpp/src/qpid/RefCounted.h23
-rw-r--r--cpp/src/qpid/sys/RefCountedMap.h197
-rw-r--r--cpp/src/tests/RefCountedMap.cpp154
3 files changed, 202 insertions, 172 deletions
diff --git a/cpp/src/qpid/RefCounted.h b/cpp/src/qpid/RefCounted.h
index bcef69d21e..c325b204d4 100644
--- a/cpp/src/qpid/RefCounted.h
+++ b/cpp/src/qpid/RefCounted.h
@@ -39,10 +39,9 @@ class AbstractRefCounted {
};
/**
- * Reference-counted virtual base class.
+ * Reference-counted base class.
*/
-class RefCounted : public AbstractRefCounted
-{
+class RefCounted : public AbstractRefCounted {
public:
RefCounted() {}
virtual void addRef() const { ++count; }
@@ -53,12 +52,26 @@ class RefCounted : public AbstractRefCounted
// Copy/assign do not copy refcounts.
RefCounted(const RefCounted&) : AbstractRefCounted() {}
RefCounted& operator=(const RefCounted&) { return *this; }
- virtual void released() const { delete this; }
+ virtual void released() const { assert(count==0); delete this; }
- private:
mutable sys::AtomicCount count;
};
+/**
+ * Reference-counted member of a reference-counted parent class.
+ * Delegates reference counts to the parent so that the parent is
+ * deleted only when there are no references to the parent or any of
+ * its children.
+ */
+struct RefCountedChild : public AbstractRefCounted {
+ protected:
+ AbstractRefCounted& parent;
+ RefCountedChild(AbstractRefCounted& parent_) : parent(parent_) {}
+ public:
+ void addRef() const { parent.addRef(); }
+ void release() const { parent.release(); }
+};
+
using boost::intrusive_ptr;
} // namespace qpid
diff --git a/cpp/src/qpid/sys/RefCountedMap.h b/cpp/src/qpid/sys/RefCountedMap.h
index 2041548667..7db1d34953 100644
--- a/cpp/src/qpid/sys/RefCountedMap.h
+++ b/cpp/src/qpid/sys/RefCountedMap.h
@@ -24,121 +24,140 @@
#include "qpid/sys/Mutex.h"
#include "qpid/RefCounted.h"
-
+#include <boost/type_traits/remove_pointer.hpp>
+#include <boost/bind.hpp>
+#include <boost/tuple/tuple.hpp>
+#include <boost/cast.hpp>
+#include <vector>
#include <map>
+#include <algorithm>
namespace qpid {
namespace sys {
-/**
- * A thread-safe, RefCounted map of RefCounted entries. Entries are
- * automatically erased when released The entire map is released when
- * all its entries are erased.
- *
- * The assumption is that some other object/thread holds an iterator
- * to each entry for as long as it is useful.
- *
- * The map can be cleared with close()
- *
- * WARNING: Assigning an intrusive_ptr<D> returned by the map locks the
- * map. To avoid deadlock, you MUST NOT modify an iterator while
- * holding another lock that could be locked as a result of erasing
- * the entry and destroying its value.
- *
- * @param D must be public RefCounted
- */
-template <class Key, class Data>
-class RefCountedMap : public RefCounted
-{
+template <class, class, class> class RefCountedMap;
+
+template <class Key, class Data, class Base=RefCounted,
+ class Impl=std::map<Key, Data*> >
+class RefCountedMapData : public Base {
public:
- typedef intrusive_ptr<Data> DataPtr;
+ typedef RefCountedMap<Key, Data, Impl> Map;
+
+ bool attached() {
+ assert(map || self->second == this);
+ return map;
+ }
+ const Key& getKey() const { assert(attached()); return self->first; }
+ void released() const { if (map) map->lockedDetach(self); delete this; }
private:
- struct Entry : public Data {
- typedef typename RefCountedMap::Iterator Iterator;
- intrusive_ptr<RefCountedMap> map;
- Iterator self;
- void init(intrusive_ptr<RefCountedMap> m, Iterator s) {
- map=m; self=s;
- }
- void released() const {
- if (map) {
- intrusive_ptr<RefCountedMap> protect(map);
- map->map.erase(self);
- }
- }
- };
+ friend class RefCountedMap<Key, Data, Impl>;
+ intrusive_ptr<Map> map;
+ typename Impl::iterator self;
- typedef std::map<Key,Entry> Map;
- typedef typename Map::iterator Iterator;
+ public:
+};
- typedef Mutex::ScopedLock Lock;
- struct OpenLock : public Lock {
- OpenLock(RefCountedMap& m) : Lock(m.lock) { assert(!m.closed); }
- };
+/**
+ * A thread-safe, reference-counted, weak map of reference-counted values.
+ *
+ * The map does not hold a reference to its members, they must have
+ * external references to exist. When last external reference is
+ * released, the value is atomically erased from the map and deleted.
+ *
+ * The map itself is a member of a ref-counted holder class, the
+ * map ensures its holder is not deleted till the map is empty.
+ */
+template <class Key, class Data, class Impl=std::map<Key, Data*> >
+class RefCountedMap : public RefCountedChild {
+ template <class, class, class, class> friend class RefCountedMapData;
+ typedef typename Impl::iterator iterator;
+ typedef typename Impl::value_type value_type;
- DataPtr ptr_(Iterator i) { return i==map.end() ? 0 : &i->second; }
+ mutable sys::Mutex lock;
+ Impl map;
+
+ // Acquire the lock and ensure map is not deleted before unlock.
+ class Lock {
+ intrusive_ptr<const RefCountedMap> map;
+ sys::Mutex::ScopedLock lock;
+ public:
+ Lock(const RefCountedMap* m) : map(m), lock(m->lock) {}
+ };
- Mutex lock;
- Map map;
- bool closed;
+ // Called from Data::released.
+ void lockedDetach(iterator i) { Lock l(this); detach(i); }
- friend struct Entry;
- friend class iterator;
+ void detach(iterator i) {
+ // Must be called with lock held.
+ assert(i->second->map == this);
+ map.erase(i);
+ i->second->map = 0; // May call this->release()
+ }
public:
- RefCountedMap() : closed(false) {}
+ RefCountedMap(RefCounted& container) : RefCountedChild(container) {}
+ ~RefCountedMap() {}
- /** Return 0 if not found
- * @pre !isClosed()
- */
- DataPtr find(const Key& k) {
- OpenLock l(*this);
- return ptr_(map.find(k));
+ /** Return 0 if not found */
+ intrusive_ptr<Data> find(const Key& k) {
+ Lock l(this);
+ iterator i = map.find(k);
+ return (i == map.end()) ? 0 : i->second;
}
- /** Find existing or create new entry for k
- * @pre !isClosed()
- */
- DataPtr get(const Key& k) {
- OpenLock l(*this);
- std::pair<Iterator,bool> ib=
- map.insert(std::make_pair(k, Entry()));
- if (ib.second)
- ib.first->second.init(this, ib.first);
- return ptr_(ib.first);
+ bool insert(const Key& k, intrusive_ptr<Data> d) {
+ Lock l(this);
+ iterator i;
+ bool inserted;
+ boost::tuples::tie(i, inserted) =
+ map.insert(std::make_pair(k, d.get()));
+ if (inserted) {
+ assert(!d->map);
+ d->map=boost::polymorphic_downcast<RefCountedMap*>(this);
+ d->self=i;
+ }
+ return inserted;
}
- size_t size() { Lock l(lock); return map.size(); }
+ size_t size() { Lock l(this); return map.size(); }
- bool empty() { return size() == 0u; }
+ bool empty() { Lock l(this); return map.empty(); }
- bool isClosed() { Lock l(lock); return closed; }
-
- /**
- * Close the map and call functor on each remaining entry.
- * Note the map will not be deleted until all entries are
- * released, the assumption is that functor takes some
- * action that will release the entry.
- *
- * close() does nothing if isClosed()
- */
- template <class F>
- void close(F functor) {
- Lock l(lock);
- if (closed) return;
- closed=true; // No more inserts
- intrusive_ptr<RefCountedMap> protect(this);
- Iterator i=map.begin();
- while (i != map.end()) {
- Iterator j=i;
- ++i;
- functor(j->second); // May erase j
+ void erase(const Key& k) { Lock l(this); detach(map.find(k)); }
+
+ void clear() { Lock l(this); while (!map.empty()) detach(map.begin()); }
+
+ /** Clear the map, apply functor to each entry before erasing */
+ template <class F> void clear(F functor) {
+ Lock l(this);
+ while (!map.empty()) {
+ intrusive_ptr<Data> ptr;
+ if (map.empty()) return;
+ ptr = map.begin()->second;
+ detach(map.begin());
+ sys::Mutex::ScopedUnlock u(lock);
+ functor(ptr);
}
}
-};
+ /** Apply functor to each map entry. */
+ template <class F> void apply(F functor) {
+ std::vector<intrusive_ptr<Data> > snapshot;
+ {
+ // Take a snapshot referencing all values in map.
+ Lock l(this);
+ snapshot.resize(map.size());
+ typedef value_type value_type;
+ std::transform(map.begin(), map.end(), snapshot.begin(),
+ boost::bind(&value_type::second, _1));
+ }
+ // Drop the lock to call functor.
+ std::for_each(snapshot.begin(), snapshot.end(), functor);
+ }
+};
+
}} // namespace qpid::sys
#endif /*!QPID_SYS_REFCOUNTEDMAP_H*/
diff --git a/cpp/src/tests/RefCountedMap.cpp b/cpp/src/tests/RefCountedMap.cpp
index 1875b8161c..79a0d94eb7 100644
--- a/cpp/src/tests/RefCountedMap.cpp
+++ b/cpp/src/tests/RefCountedMap.cpp
@@ -17,9 +17,10 @@
*/
#include "qpid/sys/RefCountedMap.h"
-
#include "unit_test.h"
+#include "test_tools.h"
#include <boost/bind.hpp>
+#include <map>
QPID_AUTO_TEST_SUITE(RefCountedMapTestSuite)
@@ -27,99 +28,96 @@ using namespace std;
using namespace qpid;
using namespace qpid::sys;
-template <int ID> struct CountEm {
+template <int Id> struct Counted {
static int instances;
- CountEm() { instances++; }
- ~CountEm() { instances--; }
- CountEm(const CountEm&) { instances++; }
+ Counted() { ++instances; }
+ Counted(const Counted&) { ++instances; }
+ ~Counted() { --instances; }
};
-template <int ID> int CountEm<ID>::instances = 0;
-
-struct Data;
+template <int Id>int Counted<Id>::instances=0;
-struct Attachment : public RefCounted, public CountEm<1> {
- intrusive_ptr<Data> link;
+struct Data : public RefCountedMapData<int, Data>, public Counted<2> {
+ Data(int i=0) : value(i) {}
+ int value;
+ void inc() { value++; }
};
-struct Data : public RefCounted, public CountEm<2> {
- intrusive_ptr<Attachment> link;
- void attach(intrusive_ptr<Attachment> a) {
- if (!a) return;
- a->link=this;
- link=a;
- }
- void detach() {
- if (!link) return;
- intrusive_ptr<Data> protect(this);
- link->link=0;
- link=0;
- }
+struct Container : public RefCounted, public Counted<3> {
+ Data::Map map;
+ Container() : map(*this) {}
};
-typedef intrusive_ptr<Data> DataPtr;
-
-struct Map : public RefCountedMap<int,Data>, public CountEm<3> {};
-
-
-
-
-BOOST_AUTO_TEST_CASE(testRefCountedMap) {
- BOOST_CHECK_EQUAL(0, Map::instances);
- BOOST_CHECK_EQUAL(0, Data::instances);
-
- intrusive_ptr<Map> map=new Map();
- BOOST_CHECK_EQUAL(1, Map::instances);
-
- // Empty map
- BOOST_CHECK(!map->isClosed());
- BOOST_CHECK(map->empty());
- BOOST_CHECK_EQUAL(map->size(), 0u);
- BOOST_CHECK(!map->find(1));
-
+
+struct RefCountedMapFixture {
+ intrusive_ptr<Container> cont;
+ intrusive_ptr<Data> p, q;
+ RefCountedMapFixture() :
+ cont(new Container()), p(new Data(1)), q(new Data(2))
{
- // Add entries
- DataPtr p=map->get(1);
- DataPtr q=map->get(2);
+ cont->map.insert(1,p);
+ cont->map.insert(2,q);
+ }
+ ~RefCountedMapFixture() { if (cont) cont->map.clear(); }
+};
- BOOST_CHECK_EQUAL(Data::instances, 2);
- BOOST_CHECK_EQUAL(map->size(), 2u);
+BOOST_FIXTURE_TEST_CASE(testFixtureSetup, RefCountedMapFixture) {
+ BOOST_CHECK_EQUAL(1, Container::instances);
+ BOOST_CHECK_EQUAL(2, Data::instances);
+ BOOST_CHECK_EQUAL(cont->map.size(), 2u);
+ BOOST_CHECK_EQUAL(cont->map.find(1)->value, 1);
+ BOOST_CHECK_EQUAL(cont->map.find(2)->value, 2);
+}
- p=0; // verify erased
- BOOST_CHECK_EQUAL(Data::instances, 1);
- BOOST_CHECK_EQUAL(map->size(), 1u);
+BOOST_FIXTURE_TEST_CASE(testReleaseRemoves, RefCountedMapFixture)
+{
+ // Release external ref, removes from map
+ p = 0;
+ BOOST_CHECK_EQUAL(Data::instances, 1);
+ BOOST_CHECK_EQUAL(cont->map.size(), 1u);
+ BOOST_CHECK(!cont->map.find(1));
+ BOOST_CHECK_EQUAL(cont->map.find(2)->value, 2);
+
+ q = 0;
+ BOOST_CHECK(cont->map.empty());
+}
- p=map->find(2);
- BOOST_CHECK(q==p);
+// Functor that releases values as a side effect.
+struct Release {
+ RefCountedMapFixture& f ;
+ Release(RefCountedMapFixture& ff) : f(ff) {}
+ void operator()(const intrusive_ptr<Data>& ptr) {
+ BOOST_CHECK(ptr->value > 0); // Make sure ptr is not released.
+ f.p = 0;
+ f.q = 0;
+ BOOST_CHECK(ptr->value > 0); // Make sure ptr is not released.
}
+};
- BOOST_CHECK(map->empty());
- BOOST_CHECK_EQUAL(1, Map::instances);
- BOOST_CHECK_EQUAL(0, Data::instances);
- {
- // Hold the map via a reference to an entry.
- DataPtr p=map->get(3);
- map=0;
- BOOST_CHECK_EQUAL(1, Map::instances); // Held by entry.
- }
- BOOST_CHECK_EQUAL(0, Map::instances); // entry released
+BOOST_FIXTURE_TEST_CASE(testApply, RefCountedMapFixture) {
+ cont->map.apply(boost::bind(&Data::inc, _1));
+ BOOST_CHECK_EQUAL(2, p->value);
+ BOOST_CHECK_EQUAL(3, q->value);
+
+ // Allow functors to release valuse as side effects.
+ cont->map.apply(Release(*this));
+ BOOST_CHECK(cont->map.empty());
+ BOOST_CHECK_EQUAL(Data::instances, 0);
}
+BOOST_FIXTURE_TEST_CASE(testClearFunctor, RefCountedMapFixture) {
+ cont->map.clear(boost::bind(&Data::inc, _1));
+ BOOST_CHECK(cont->map.empty());
+ BOOST_CHECK_EQUAL(2, p->value);
+ BOOST_CHECK_EQUAL(3, q->value);
+}
-BOOST_AUTO_TEST_CASE(testRefCountedMapAttachClose) {
- intrusive_ptr<Map> map=new Map();
- DataPtr d=map->get(5);
- d->attach(new Attachment());
- d=0;
- // Attachment keeps entry pinned
- BOOST_CHECK_EQUAL(1u, map->size());
- BOOST_CHECK(map->find(5));
-
- // Close breaks attachment
- map->close(boost::bind(&Data::detach, _1));
- BOOST_CHECK(map->empty());
- BOOST_CHECK(map->isClosed());
- BOOST_CHECK_EQUAL(0, Data::instances);
- BOOST_CHECK_EQUAL(0, Attachment::instances);
+BOOST_FIXTURE_TEST_CASE(testReleaseEmptyMap, RefCountedMapFixture) {
+ // Container must not be deleted till map is empty.
+ cont = 0;
+ BOOST_CHECK_EQUAL(1, Container::instances); // Not empty.
+ p = 0;
+ q = 0;
+ BOOST_CHECK_EQUAL(0, Container::instances); // Deleted
}
QPID_AUTO_TEST_SUITE_END()