NO-OP: whitespace, doxygen formatting and naming

This commit is contained in:
Robin Gareus 2020-11-10 17:02:26 +01:00
parent f87de76b9f
commit b1846a578d
No known key found for this signature in database
GPG key ID: A090BCE02CF57F04

View file

@ -47,125 +47,129 @@
* 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.
*/ */
} }
bool update (boost::shared_ptr<T> new_value) bool update (boost::shared_ptr<T> new_value)
@ -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__ */