mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-10 16:46:35 +01:00
NO-OP: whitespace, doxygen formatting and naming
This commit is contained in:
parent
f87de76b9f
commit
b1846a578d
1 changed files with 146 additions and 133 deletions
|
|
@ -47,124 +47,128 @@
|
||||||
* The RCU manager will manage the various instances of "the managed object" in a way that is transparent to users of the manager
|
* 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.
|
* and managed object.
|
||||||
*/
|
*/
|
||||||
template<class T>
|
template <class T>
|
||||||
class /*LIBPBD_API*/ RCUManager
|
class /*LIBPBD_API*/ RCUManager
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
RCUManager (T* new_rcu_value)
|
||||||
RCUManager (T* new_rcu_value) : active_reads(0) {
|
: active_reads (0)
|
||||||
x.m_rcu_value = new boost::shared_ptr<T> (new_rcu_value);
|
{
|
||||||
|
x.rcu_value = new boost::shared_ptr<T> (new_rcu_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~RCUManager() { delete x.m_rcu_value; }
|
virtual ~RCUManager ()
|
||||||
|
{
|
||||||
|
delete x.rcu_value;
|
||||||
|
}
|
||||||
|
|
||||||
boost::shared_ptr<T> reader () const {
|
boost::shared_ptr<T> reader () const
|
||||||
|
{
|
||||||
boost::shared_ptr<T> rv;
|
boost::shared_ptr<T> rv;
|
||||||
|
|
||||||
// Keep count of any readers in this section of code, so writers can
|
/* Keep count of any readers in this section of code, so writers can
|
||||||
// wait until m_rcu_value is no longer in use after an atomic exchange
|
* wait until rcu_value is no longer in use after an atomic exchange
|
||||||
// before dropping it.
|
* before dropping it.
|
||||||
g_atomic_int_inc(&active_reads);
|
*/
|
||||||
rv = *((boost::shared_ptr<T> *) g_atomic_pointer_get (&x.gptr));
|
g_atomic_int_inc (&active_reads);
|
||||||
g_atomic_int_dec_and_test(&active_reads);
|
rv = *((boost::shared_ptr<T>*)g_atomic_pointer_get (&x.gptr));
|
||||||
|
g_atomic_int_dec_and_test (&active_reads);
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* this is an abstract base class - how these are implemented depends on the assumptions
|
/* 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
|
* that one can make about the users of the RCUManager. See SerializedRCUManager below
|
||||||
for one implementation.
|
* for one implementation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
virtual boost::shared_ptr<T> write_copy () = 0;
|
virtual boost::shared_ptr<T> write_copy () = 0;
|
||||||
virtual bool update (boost::shared_ptr<T> new_value) = 0;
|
virtual bool update (boost::shared_ptr<T> new_value) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/* ordinarily this would simply be a declaration of a ptr to a shared_ptr<T>. however, the atomic
|
/* 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
|
* 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
|
* 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
|
* 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
|
* we will use x.rcu_value, but when we need to use an atomic op, we use x.gptr. Both expressions
|
||||||
evaluate to the same address.
|
* evaluate to the same address.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
union {
|
union {
|
||||||
boost::shared_ptr<T>* m_rcu_value;
|
boost::shared_ptr<T>* rcu_value;
|
||||||
mutable volatile gpointer gptr;
|
mutable volatile gpointer gptr;
|
||||||
} x;
|
} x;
|
||||||
|
|
||||||
mutable volatile gint active_reads;
|
mutable volatile gint active_reads;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/** 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
|
* 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
|
* RT time constraints, and writers who are not. Therefore, we do not care how
|
||||||
slow the write_copy()/update() operations are, or what synchronization
|
* slow the write_copy()/update() operations are, or what synchronization
|
||||||
primitives they use.
|
* primitives they use.
|
||||||
|
*
|
||||||
Because of this design assumption, this class will serialize all
|
* Because of this design assumption, this class will serialize all
|
||||||
writers. That is, objects calling write_copy()/update() will be serialized by
|
* 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();
|
* 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
|
* 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
|
* execution of multiple writers if more than one is blocked in this way is
|
||||||
undefined.
|
* undefined.
|
||||||
|
*
|
||||||
The class maintains a lock-protected "dead wood" list of old value of
|
* 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
|
* *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
|
* write_copy(). If the list is the last instance of a shared_ptr<T> that
|
||||||
references the object (determined by shared_ptr::unique()) 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
|
* erase it from the list, thus deleting the object it points to. This is lazy
|
||||||
destruction - the SerializedRCUManager assumes that there will sufficient
|
* destruction - the SerializedRCUManager assumes that there will sufficient
|
||||||
calls to write_copy() to ensure that we do not inadvertently leave objects
|
* calls to write_copy() to ensure that we do not inadvertently leave objects
|
||||||
around for excessive periods of time.
|
* around for excessive periods of time.
|
||||||
|
*
|
||||||
For extremely well defined circumstances (i.e. it is known that there are no
|
* For extremely well defined circumstances (i.e. it is known that there are no
|
||||||
other writer objects in existence), SerializedRCUManager also provides a
|
* other writer objects in existence), SerializedRCUManager also provides a
|
||||||
flush() method that will unconditionally clear out the "dead wood" list. It
|
* 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>
|
* 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.
|
* means that no actual objects will be deleted incorrectly if this is misused.
|
||||||
*/
|
*/
|
||||||
template<class T>
|
template <class T>
|
||||||
class /*LIBPBD_API*/ SerializedRCUManager : public RCUManager<T>
|
class /*LIBPBD_API*/ SerializedRCUManager : public RCUManager<T>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
SerializedRCUManager (T* new_rcu_value)
|
||||||
SerializedRCUManager(T* new_rcu_value)
|
: RCUManager<T> (new_rcu_value)
|
||||||
: RCUManager<T>(new_rcu_value)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::shared_ptr<T> write_copy ()
|
boost::shared_ptr<T> write_copy ()
|
||||||
{
|
{
|
||||||
m_lock.lock();
|
_lock.lock ();
|
||||||
|
|
||||||
// clean out any dead wood
|
// clean out any dead wood
|
||||||
|
|
||||||
typename std::list<boost::shared_ptr<T> >::iterator i;
|
typename std::list<boost::shared_ptr<T> >::iterator i;
|
||||||
|
|
||||||
for (i = m_dead_wood.begin(); i != m_dead_wood.end(); ) {
|
for (i = _dead_wood.begin (); i != _dead_wood.end ();) {
|
||||||
if ((*i).unique()) {
|
if ((*i).unique ()) {
|
||||||
i = m_dead_wood.erase (i);
|
i = _dead_wood.erase (i);
|
||||||
} else {
|
} else {
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* store the current so that we can do compare and exchange
|
/* store the current so that we can do compare and exchange
|
||||||
when someone calls update(). Notice that we hold
|
* when someone calls update(). Notice that we hold
|
||||||
a lock, so this store of m_rcu_value is atomic.
|
* a lock, so this store of rcu_value is atomic.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
current_write_old = RCUManager<T>::x.m_rcu_value;
|
_current_write_old = RCUManager<T>::x.rcu_value;
|
||||||
|
|
||||||
boost::shared_ptr<T> new_copy (new T(**current_write_old));
|
boost::shared_ptr<T> new_copy (new T (**_current_write_old));
|
||||||
|
|
||||||
return new_copy;
|
return new_copy;
|
||||||
|
|
||||||
/* notice that the write lock is still held: update() MUST
|
/* notice that the write lock is still held: update() MUST
|
||||||
be called or we will cause another writer to stall.
|
* be called or we will cause another writer to stall.
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,113 +179,122 @@ public:
|
||||||
boost::shared_ptr<T>* new_spp = new boost::shared_ptr<T> (new_value);
|
boost::shared_ptr<T>* new_spp = new boost::shared_ptr<T> (new_value);
|
||||||
|
|
||||||
/* update, by atomic compare&swap. Only succeeds if the old
|
/* update, by atomic compare&swap. Only succeeds if the old
|
||||||
value has not been changed.
|
* value has not been changed.
|
||||||
|
*
|
||||||
XXX but how could it? we hold the freakin' lock!
|
* XXX but how could it? we hold the freakin' lock!
|
||||||
*/
|
*/
|
||||||
|
|
||||||
bool ret = g_atomic_pointer_compare_and_exchange (&RCUManager<T>::x.gptr,
|
bool ret = g_atomic_pointer_compare_and_exchange (&RCUManager<T>::x.gptr,
|
||||||
(gpointer) current_write_old,
|
(gpointer)_current_write_old,
|
||||||
(gpointer) new_spp);
|
(gpointer)new_spp);
|
||||||
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
|
/* successful update
|
||||||
|
*
|
||||||
|
* wait until there are no active readers. This ensures that any
|
||||||
|
* references to the old value have been fully copied into a new
|
||||||
|
* shared_ptr, and thus have had their reference count incremented.
|
||||||
|
*/
|
||||||
|
|
||||||
// successful update
|
for (unsigned i = 0; g_atomic_int_get (&(RCUManager<T>::active_reads)) != 0; ++i) {
|
||||||
|
|
||||||
// wait until there are no active readers. This ensures that any
|
|
||||||
// references to the old value have been fully copied into a new
|
|
||||||
// shared_ptr, and thus have had their reference count incremented.
|
|
||||||
|
|
||||||
for (unsigned i = 0; g_atomic_int_get(&(RCUManager<T>::active_reads)) != 0; ++i) {
|
|
||||||
// spin being nice to the scheduler/CPU
|
// spin being nice to the scheduler/CPU
|
||||||
boost::detail::yield(i);
|
boost::detail::yield (i);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we are not the only user, put the old value into dead_wood.
|
/* if we are not the only user, put the old value into dead_wood.
|
||||||
// if we are the only user, then it is safe to drop it here.
|
* if we are the only user, then it is safe to drop it here.
|
||||||
|
*/
|
||||||
|
|
||||||
if (!current_write_old->unique()) {
|
if (!_current_write_old->unique ()) {
|
||||||
m_dead_wood.push_back (*current_write_old);
|
_dead_wood.push_back (*_current_write_old);
|
||||||
}
|
}
|
||||||
|
|
||||||
// now delete it - if we are the only user, this deletes the
|
/* now delete it - if we are the only user, this deletes the
|
||||||
// underlying object. if other users existed, then there will
|
* underlying object. if other users existed, then there will
|
||||||
// be an extra reference in m_dead_wood, ensuring that the
|
* be an extra reference in _dead_wood, ensuring that the
|
||||||
// underlying object lives on even when the other users
|
* underlying object lives on even when the other users
|
||||||
// are done with it
|
* are done with it
|
||||||
|
*/
|
||||||
|
|
||||||
delete current_write_old;
|
delete _current_write_old;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* unlock, allowing other writers to proceed */
|
/* unlock, allowing other writers to proceed */
|
||||||
|
|
||||||
m_lock.unlock();
|
_lock.unlock ();
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void flush () {
|
void flush ()
|
||||||
Glib::Threads::Mutex::Lock lm (m_lock);
|
{
|
||||||
m_dead_wood.clear ();
|
Glib::Threads::Mutex::Lock lm (_lock);
|
||||||
|
_dead_wood.clear ();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Glib::Threads::Mutex m_lock;
|
Glib::Threads::Mutex _lock;
|
||||||
boost::shared_ptr<T>* current_write_old;
|
boost::shared_ptr<T>* _current_write_old;
|
||||||
std::list<boost::shared_ptr<T> > m_dead_wood;
|
std::list<boost::shared_ptr<T> > _dead_wood;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** RCUWriter is a convenience object that implements write_copy/update via
|
/** RCUWriter is a convenience object that implements write_copy/update via
|
||||||
lifetime management. Creating the object obtains a writable copy, which can
|
* lifetime management. Creating the object obtains a writable copy, which can
|
||||||
be obtained via the get_copy() method; deleting the object will update
|
* be obtained via the get_copy() method; deleting the object will update
|
||||||
the manager's copy. Code doing a write/update thus looks like:
|
* the manager's copy. Code doing a write/update thus looks like:
|
||||||
|
*
|
||||||
{
|
* @code
|
||||||
|
* {
|
||||||
RCUWriter writer (object_manager);
|
* RCUWriter writer (object_manager);
|
||||||
boost::shared_ptr<T> copy = writer.get_copy();
|
* boost::shared_ptr<T> copy = writer.get_copy();
|
||||||
... modify copy ...
|
* ... modify copy ...
|
||||||
|
*
|
||||||
} <= writer goes out of scope, update invoked
|
* } <= writer goes out of scope, update invoked
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
template<class T>
|
template <class T>
|
||||||
class /*LIBPBD_API*/ RCUWriter
|
class /*LIBPBD_API*/ RCUWriter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
RCUWriter (RCUManager<T>& manager)
|
||||||
|
: _manager (manager)
|
||||||
|
, _copy (_manager.write_copy ())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
RCUWriter(RCUManager<T>& manager)
|
~RCUWriter ()
|
||||||
: m_manager(manager)
|
{
|
||||||
, m_copy (m_manager.write_copy()) {}
|
if (_copy.unique ()) {
|
||||||
|
|
||||||
~RCUWriter() {
|
|
||||||
if (m_copy.unique()) {
|
|
||||||
/* As intended, our copy is the only reference
|
/* As intended, our copy is the only reference
|
||||||
to the object pointed to by m_copy. Update
|
to the object pointed to by _copy. Update
|
||||||
the manager with the (presumed) modified
|
the manager with the (presumed) modified
|
||||||
version.
|
version.
|
||||||
*/
|
*/
|
||||||
m_manager.update(m_copy);
|
_manager.update (_copy);
|
||||||
} else {
|
} else {
|
||||||
/* This means that some other object is using our copy
|
/* This means that some other object is using our copy
|
||||||
of the object. This can only happen if the scope in
|
* of the object. This can only happen if the scope in
|
||||||
which this RCUWriter exists passed it to a function
|
* which this RCUWriter exists passed it to a function
|
||||||
that created a persistent reference to it, since the
|
* that created a persistent reference to it, since the
|
||||||
copy was private to this particular RCUWriter. Doing
|
* copy was private to this particular RCUWriter. Doing
|
||||||
so will not actually break anything but it violates
|
* so will not actually break anything but it violates
|
||||||
the design intention here and so we do not bother to
|
* 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?
|
* XXX should we print a warning about this?
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::shared_ptr<T> get_copy() const { return m_copy; }
|
boost::shared_ptr<T> get_copy () const
|
||||||
|
{
|
||||||
|
return _copy;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RCUManager<T>& m_manager;
|
RCUManager<T>& _manager;
|
||||||
boost::shared_ptr<T> m_copy;
|
boost::shared_ptr<T> _copy;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* __pbd_rcu_h__ */
|
#endif /* __pbd_rcu_h__ */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue