X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fpbd%2Fpbd%2Frcu.h;h=7b053462946f0fcf047b25a8060b74d53b4c5f45;hb=7a6e86c9f9bef8db74b755db659794c4a859f36d;hp=4c252c87d157aae44e48a96f407e23b22df88a2e;hpb=c3f6ab344ce2a65767e2afcc83ffc24b6054e61d;p=ardour.git diff --git a/libs/pbd/pbd/rcu.h b/libs/pbd/pbd/rcu.h index 4c252c87d1..7b05346294 100644 --- a/libs/pbd/pbd/rcu.h +++ b/libs/pbd/pbd/rcu.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2000-2007 Paul Davis + Copyright (C) 2000-2007 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,38 +22,39 @@ #include "boost/shared_ptr.hpp" #include "glibmm/thread.h" - -#include -/* 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. +#include + +/** @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 and an RCUWriter. +*/ + +/** 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 + - 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. + 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. + 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 RCUManager { public: - + RCUManager (T* new_rcu_value) { x.m_rcu_value = new boost::shared_ptr (new_rcu_value); } - + virtual ~RCUManager() { delete x.m_rcu_value; } - - boost::shared_ptr reader () const { return *((boost::shared_ptr *) g_atomic_pointer_get (&x.gptr)); } - + + boost::shared_ptr reader () const { return *((boost::shared_ptr *) 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. @@ -65,7 +66,7 @@ class RCUManager protected: /* ordinarily this would simply be a declaration of a ptr to a shared_ptr. 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() + 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 @@ -79,12 +80,12 @@ class RCUManager }; -/* Serialized RCUManager implements the RCUManager interface. It is based on the +/** 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(); @@ -95,7 +96,7 @@ class RCUManager The class maintains a lock-protected "dead wood" list of old value of *m_rcu_value (i.e. shared_ptr). The list is cleaned up every time we call write_copy(). If the list is the last instance of a shared_ptr that - references the object (determined by inspecting its use_count()) then we + references the object (determined by shared_ptr::unique()) 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 @@ -107,19 +108,16 @@ class RCUManager must be used with significant caution, although the use of shared_ptr means that no actual objects will be deleted incorrectly if this is misused. */ - - template class SerializedRCUManager : public RCUManager { public: - + SerializedRCUManager(T* new_rcu_value) : RCUManager(new_rcu_value) { - } - + boost::shared_ptr write_copy () { m_lock.lock(); @@ -129,7 +127,7 @@ public: typename std::list >::iterator i; for (i = m_dead_wood.begin(); i != m_dead_wood.end(); ) { - if ((*i).use_count() == 1) { + if ((*i).unique()) { i = m_dead_wood.erase (i); } else { ++i; @@ -142,7 +140,7 @@ public: */ current_write_old = RCUManager::x.m_rcu_value; - + boost::shared_ptr new_copy (new T(**current_write_old)); return new_copy; @@ -151,7 +149,7 @@ public: be called or we will cause another writer to stall. */ } - + bool update (boost::shared_ptr new_value) { /* we still hold the write lock - other writers are locked out */ @@ -160,14 +158,14 @@ public: /* 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::x.gptr, (gpointer) current_write_old, (gpointer) new_spp); - + if (ret) { // successful update : put the old value into dead_wood, @@ -192,15 +190,15 @@ public: Glib::Mutex::Lock lm (m_lock); m_dead_wood.clear (); } - + private: - Glib::Mutex m_lock; + Glib::Mutex m_lock; boost::shared_ptr* current_write_old; std::list > 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 +/** RCUWriter is a convenience object that implements write_copy/update via + lifetime management. Creating the object obtains 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: @@ -213,19 +211,18 @@ private: } <= writer goes out of scope, update invoked */ - template class RCUWriter { public: - + RCUWriter(RCUManager& manager) : m_manager(manager) { - m_copy = m_manager.write_copy(); + m_copy = m_manager.write_copy(); } - + ~RCUWriter() { - if(m_copy.use_count() == 1) { + if (m_copy.unique()) { /* As intended, our copy is the only reference to the object pointed to by m_copy. Update the manager with the (presumed) modified @@ -240,16 +237,16 @@ public: 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. + update the manager's copy. XXX should we print a warning about this? */ } - + } - + boost::shared_ptr get_copy() const { return m_copy; } - + private: RCUManager& m_manager; boost::shared_ptr m_copy;