summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Davis <paul@linuxaudiosystems.com>2009-11-01 16:25:52 +0000
committerPaul Davis <paul@linuxaudiosystems.com>2009-11-01 16:25:52 +0000
commitc3f6ab344ce2a65767e2afcc83ffc24b6054e61d (patch)
treee09e9f5645958d6373a66189152a3883621b76c1
parent54269c883ee71bbf6df47fb515878c09129dc98d (diff)
add lots of comments to rcu.h (with concomittant full recompile)
git-svn-id: svn://localhost/ardour2/branches/3.0@5994 d708f5d6-7413-0410-9779-e7cbd77b26cf
-rw-r--r--libs/pbd/pbd/rcu.h127
1 files changed, 110 insertions, 17 deletions
diff --git a/libs/pbd/pbd/rcu.h b/libs/pbd/pbd/rcu.h
index 8b8f693e7e..4c252c87d1 100644
--- a/libs/pbd/pbd/rcu.h
+++ b/libs/pbd/pbd/rcu.h
@@ -25,6 +25,22 @@
#include <list>
+/* This header file defines a set of classes to implement Read-Copy-Update. we do not attempt to define RCU here - use google.
+
+ The design consists of two parts. An RCUManager is an object which takes over management of a pointer to another object.
+ It provides three key methods:
+
+ - reader() : obtains a shared pointer to the managed object that may be used for reading, without synchronization
+ - write_copy() : obtains a shared pointer to the object that may be used for writing/modification
+ - update() : accepts a shared pointer to a (presumed) modified instance of the object and causes all
+ future reader() and write_copy() calls to use that instance.
+
+ Any existing users of the value returned by reader() can continue to use their copy even as a write_copy()/update() takes place.
+ The RCU manager will manage the various instances of "the managed object" in a way that is transparent to users of the manager
+ and managed object.
+*/
+
+
template<class T>
class RCUManager
{
@@ -38,15 +54,59 @@ class RCUManager
boost::shared_ptr<T> reader () const { return *((boost::shared_ptr<T> *) g_atomic_pointer_get (&x.gptr)); }
+ /* this is an abstract base class - how these are implemented depends on the assumptions
+ that one can make about the users of the RCUManager. See SerializedRCUManager below
+ for one implementation.
+ */
+
virtual boost::shared_ptr<T> write_copy () = 0;
virtual bool update (boost::shared_ptr<T> new_value) = 0;
protected:
+ /* ordinarily this would simply be a declaration of a ptr to a shared_ptr<T>. however, the atomic
+ operations that we are using (from glib) have sufficiently strict typing that it proved hard
+ to get them to accept even a cast value of the ptr-to-shared-ptr() as the argument to get()
+ and comp_and_exchange(). Consequently, we play a litle trick here that relies on the fact
+ that sizeof(A*) == sizeof(B*) no matter what the types of A and B are. for most purposes
+ we will use x.m_rcu_value, but when we need to use an atomic op, we use x.gptr. Both expressions
+ evaluate to the same address.
+ */
+
union {
boost::shared_ptr<T>* m_rcu_value;
mutable volatile gpointer gptr;
} x;
};
+
+
+/* Serialized RCUManager implements the RCUManager interface. It is based on the
+ following key assumption: among its users we have readers that are bound by
+ RT time constraints, and writers who are not. Therefore, we do not care how
+ slow the write_copy()/update() operations are, or what synchronization
+ primitives they use.
+
+ Because of this design assumption, this class will serialize all
+ writers. That is, objects calling write_copy()/update() will be serialized by
+ a mutex. Only a single writer may be in the middle of write_copy()/update();
+ all other writers will block until the first has finished. The order of
+ execution of multiple writers if more than one is blocked in this way is
+ undefined.
+
+ The class maintains a lock-protected "dead wood" list of old value of
+ *m_rcu_value (i.e. shared_ptr<T>). The list is cleaned up every time we call
+ write_copy(). If the list is the last instance of a shared_ptr<T> that
+ references the object (determined by inspecting its use_count()) then we
+ erase it from the list, thus deleting the object it points to. This is lazy
+ destruction - the SerializedRCUManager assumes that there will sufficient
+ calls to write_copy() to ensure that we do not inadvertently leave objects
+ around for excessive periods of time.
+
+ For extremely well defined circumstances (i.e. it is known that there are no
+ other writer objects in existence), SerializedRCUManager also provides a
+ flush() method that will unconditionally clear out the "dead wood" list. It
+ must be used with significant caution, although the use of shared_ptr<T>
+ means that no actual objects will be deleted incorrectly if this is misused.
+*/
template<class T>
@@ -76,23 +136,33 @@ public:
}
}
- // store the current
+ /* store the current so that we can do compare and exchange
+ when someone calls update(). Notice that we hold
+ a lock, so this store of m_rcu_value is atomic.
+ */
current_write_old = RCUManager<T>::x.m_rcu_value;
boost::shared_ptr<T> new_copy (new T(**current_write_old));
return new_copy;
+
+ /* notice that the write lock is still held: update() MUST
+ be called or we will cause another writer to stall.
+ */
}
bool update (boost::shared_ptr<T> new_value)
{
- // we hold the lock at this point effectively blocking
- // other writers.
+ /* we still hold the write lock - other writers are locked out */
boost::shared_ptr<T>* new_spp = new boost::shared_ptr<T> (new_value);
- // update, checking that nobody beat us to it
+ /* update, by atomic compare&swap. Only succeeds if the old
+ value has not been changed.
+
+ XXX but how could it? we hold the freakin' lock!
+ */
bool ret = g_atomic_pointer_compare_and_exchange (&RCUManager<T>::x.gptr,
(gpointer) current_write_old,
@@ -111,6 +181,8 @@ public:
delete current_write_old;
}
+ /* unlock, allowing other writers to proceed */
+
m_lock.unlock();
return ret;
@@ -126,6 +198,21 @@ private:
boost::shared_ptr<T>* current_write_old;
std::list<boost::shared_ptr<T> > m_dead_wood;
};
+
+/* RCUWriter is a convenience object that implements write_copy/update via
+ lifetime management. Creating the object obtais a writable copy, which can
+ be obtained via the get_copy() method; deleting the object will update
+ the manager's copy. Code doing a write/update thus looks like:
+
+ {
+
+ RCUWriter writer (object_manager);
+ boost::shared_ptr<T> copy = writer.get_copy();
+ ... modify copy ...
+
+ } <= writer goes out of scope, update invoked
+
+*/
template<class T>
class RCUWriter
@@ -133,32 +220,38 @@ class RCUWriter
public:
RCUWriter(RCUManager<T>& manager)
- : m_manager(manager)
- {
+ : m_manager(manager) {
m_copy = m_manager.write_copy();
}
- ~RCUWriter()
- {
- // we can check here that the refcount of m_copy is 1
-
+ ~RCUWriter() {
if(m_copy.use_count() == 1) {
+ /* As intended, our copy is the only reference
+ to the object pointed to by m_copy. Update
+ the manager with the (presumed) modified
+ version.
+ */
m_manager.update(m_copy);
} else {
-
- // critical error.
+ /* This means that some other object is using our copy
+ of the object. This can only happen if the scope in
+ which this RCUWriter exists passed it to a function
+ that created a persistent reference to it, since the
+ copy was private to this particular RCUWriter. Doing
+ so will not actually break anything but it violates
+ the design intention here and so we do not bother to
+ update the manager's copy.
+
+ XXX should we print a warning about this?
+ */
}
}
- // or operator boost::shared_ptr<T> ();
- boost::shared_ptr<T> get_copy() { return m_copy; }
+ boost::shared_ptr<T> get_copy() const { return m_copy; }
private:
-
RCUManager<T>& m_manager;
-
- // preferably this holds a pointer to T
boost::shared_ptr<T> m_copy;
};