diff options
author | Paul Davis <paul@linuxaudiosystems.com> | 2009-11-01 16:25:52 +0000 |
---|---|---|
committer | Paul Davis <paul@linuxaudiosystems.com> | 2009-11-01 16:25:52 +0000 |
commit | c3f6ab344ce2a65767e2afcc83ffc24b6054e61d (patch) | |
tree | e09e9f5645958d6373a66189152a3883621b76c1 | |
parent | 54269c883ee71bbf6df47fb515878c09129dc98d (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.h | 127 |
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; }; |