Ardour  9.0-pre0-582-g084a23a80d
rcu.h
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2000-2013 Paul Davis <paul@linuxaudiosystems.com>
3  * Copyright (C) 2009 David Robillard <d@drobilla.net>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #pragma once
21 
22 #include <atomic>
23 #include <mutex>
24 #include <memory>
25 
26 #include "boost/smart_ptr/detail/yield_k.hpp"
27 
28 #include <list>
29 
30 #include "pbd/libpbd_visibility.h"
31 
51 template <class T>
52 class /*LIBPBD_API*/ RCUManager
53 {
54 public:
55  RCUManager (T* object_to_be_managed)
56  {
57  _active_reads = 0;
58  managed_object = new std::shared_ptr<T> (object_to_be_managed);
59  }
60 
61  virtual ~RCUManager ()
62  {
63  /* This just deletes the shared ptr, but of course this may
64  also be the last reference to the managed object.
65  */
66  delete managed_object.load ();
67  }
68 
69  std::shared_ptr<T const> reader () const
70  {
71  std::shared_ptr<T> rv;
72 
73  /* Keep count of any readers in this section of code, so writers can
74  * wait until managed_object is no longer in use after an atomic exchange
75  * before dropping it.
76  */
77  /* no reads or writes below this atomic store (e.g. the copying
78  * of *managed_object) can move before this "barrier".
79  */
80  _active_reads.fetch_add (1, std::memory_order_release);
81  rv = *managed_object;
82  /* no reads or writes below this atomic store (e.g. the copying
83  * of *managed_object) can move before this "barrier", and this
84  * also synchronizes with a memory_order_acquire load when
85  * testing for active readers (see below).
86  */
87  _active_reads.fetch_sub (1, std::memory_order_release);
88 
89  return rv;
90  }
91 
92  /* this is an abstract base class - how these are implemented depends on the assumptions
93  * that one can make about the users of the RCUManager. See SerializedRCUManager below
94  * for one implementation.
95  */
96 
97  virtual std::shared_ptr<T> write_copy () = 0;
98  virtual bool update (std::shared_ptr<T> new_value) = 0;
99 
100 protected:
101  typedef std::shared_ptr<T>* PtrToSharedPtr;
102  std::atomic<PtrToSharedPtr> managed_object;
103 
104  inline bool active_read () const {
105  return _active_reads.load (std::memory_order_acquire) != 0;
106  }
107 
108 private:
109  mutable std::atomic<int> _active_reads;
110 };
111 
140 template <class T>
141 class /*LIBPBD_API*/ SerializedRCUManager : public RCUManager<T>
142 {
143 public:
144  SerializedRCUManager(T* new_managed_object)
145  : RCUManager<T>(new_managed_object)
146  , _current_write_old (0)
147  {
148  }
149 
150  void init (std::shared_ptr<T> object_to_be_managed) {
151  assert (*RCUManager<T>::managed_object == std::shared_ptr<T> ());
152  RCUManager<T>::managed_object = new std::shared_ptr<T> (object_to_be_managed);
153  }
154 
155  std::shared_ptr<T> write_copy ()
156  {
157  _lock.lock ();
158 
159  // clean out any dead wood
160 
161  typename std::list<std::shared_ptr<T> >::iterator i;
162 
163  for (i = _dead_wood.begin (); i != _dead_wood.end ();) {
164  if ((*i).unique ()) {
165  i = _dead_wood.erase (i);
166  } else {
167  ++i;
168  }
169  }
170 
171  /* store the current so that we can do compare and exchange
172  * when someone calls update(). Notice that we hold
173  * a lock, so this store of managed_object is atomic.
174  */
175 
177 
178  /* now do the (potentially arbitrarily expensive data copy of
179  * the RCU-managed object
180  */
181 
182  std::shared_ptr<T> new_copy (new T (**_current_write_old));
183 
184  return new_copy;
185 
186  /* notice that the write lock is still held: update() or abort() MUST
187  * be called or we will cause another writer to stall.
188  */
189  }
190 
191  void abort () {
192  _lock.unlock();
193  }
194 
195  bool update (std::shared_ptr<T> new_value)
196  {
197  /* we still hold the write lock - other writers are locked out */
198 
199  typename RCUManager<T>::PtrToSharedPtr new_spp = new std::shared_ptr<T> (new_value);
200 
201  /* update, by atomic compare&swap. Only succeeds if the old
202  * value has not been changed.
203  *
204  * XXX but how could it? we hold the freakin' lock!
205  */
206 
207  bool ret = RCUManager<T>::managed_object.compare_exchange_strong (_current_write_old, new_spp);
208 
209  if (ret) {
210  /* successful update
211  *
212  * wait until there are no active readers. This ensures that any
213  * references to the old value have been fully copied into a new
214  * shared_ptr, and thus have had their reference count incremented.
215  */
216 
217  for (unsigned i = 0; RCUManager<T>::active_read (); ++i) {
218  /* spin being nice to the scheduler/CPU */
219  boost::detail::yield (i);
220  }
221 
222 #if 0 // TODO find a good solition here...
223  /* if we are not the only user, put the old value into dead_wood.
224  * if we are the only user, then it is safe to drop it here.
225  */
226 
227  if (!_current_write_old->unique ()) {
228  _dead_wood.push_back (*_current_write_old);
229  }
230 #else
231  /* above ->unique() condition is subject to a race condition.
232  *
233  * Particulalry with JACK2 graph-order callbacks arriving
234  * concurrently to processing, which can lead to heap-use-after-free
235  * of the RouteList.
236  *
237  * std::shared_ptr<T>::use_count documetation reads:
238  * > In multithreaded environment, the value returned by use_count is approximate
239  * > (typical implementations use a memory_order_relaxed load).
240  */
241  _dead_wood.push_back (*_current_write_old);
242 #endif
243 
244  /* now delete it - if we are the only user, this deletes the
245  * underlying object. If other users existed, then there will
246  * be an extra reference in _dead_wood, ensuring that the
247  * underlying object lives on even when the other users
248  * are done with it
249  */
250 
251  delete _current_write_old;
252  }
253 
254  /* unlock, allowing other writers to proceed */
255 
256  _lock.unlock ();
257 
258  return ret;
259  }
260 
261  void no_update () {
262  /* just releases the lock, in the event that no changes are
263  made to a write copy.
264  */
265  _lock.unlock ();
266  }
267 
268  void flush ()
269  {
270  std::lock_guard<std::mutex> lm (_lock);
271  _dead_wood.clear ();
272  }
273 
274 private:
275  std::mutex _lock;
277  std::list<std::shared_ptr<T> > _dead_wood;
278 };
279 
295 template <class T>
296 class /*LIBPBD_API*/ RCUWriter
297 {
298 public:
300  : _manager (manager)
301  , _copy (_manager.write_copy ())
302  {
303  }
304 
306  {
307  if (_copy.unique ()) {
308  /* As intended, our copy is the only reference
309  to the object pointed to by _copy. Update
310  the manager with the (presumed) modified
311  version.
312  */
313  _manager.update (_copy);
314  } else {
315  /* This means that some other object is using our copy
316  * of the object. This can only happen if the scope in
317  * which this RCUWriter exists passed it to a function
318  * that created a persistent reference to it, since the
319  * copy was private to this particular RCUWriter. Doing
320  * so will not actually break anything but it violates
321  * the design intention here and so we do not bother to
322  * update the manager's copy.
323  *
324  * XXX should we print a warning about this?
325  */
326  }
327  }
328 
329  std::shared_ptr<T> get_copy () const
330  {
331  return _copy;
332  }
333 
334 private:
336  std::shared_ptr<T> _copy;
337 };
338 
Definition: rcu.h:53
std::shared_ptr< T > * PtrToSharedPtr
Definition: rcu.h:101
virtual bool update(std::shared_ptr< T > new_value)=0
std::atomic< PtrToSharedPtr > managed_object
Definition: rcu.h:102
virtual std::shared_ptr< T > write_copy()=0
std::atomic< int > _active_reads
Definition: rcu.h:109
std::shared_ptr< T const > reader() const
Definition: rcu.h:69
virtual ~RCUManager()
Definition: rcu.h:61
bool active_read() const
Definition: rcu.h:104
RCUManager(T *object_to_be_managed)
Definition: rcu.h:55
Definition: rcu.h:297
std::shared_ptr< T > get_copy() const
Definition: rcu.h:329
RCUManager< T > & _manager
Definition: rcu.h:335
RCUWriter(RCUManager< T > &manager)
Definition: rcu.h:299
std::shared_ptr< T > _copy
Definition: rcu.h:336
~RCUWriter()
Definition: rcu.h:305
void flush()
Definition: rcu.h:268
void no_update()
Definition: rcu.h:261
RCUManager< T >::PtrToSharedPtr _current_write_old
Definition: rcu.h:276
std::shared_ptr< T > write_copy()
Definition: rcu.h:155
void init(std::shared_ptr< T > object_to_be_managed)
Definition: rcu.h:150
void abort()
Definition: rcu.h:191
SerializedRCUManager(T *new_managed_object)
Definition: rcu.h:144
std::mutex _lock
Definition: rcu.h:275
std::list< std::shared_ptr< T > > _dead_wood
Definition: rcu.h:277
bool update(std::shared_ptr< T > new_value)
Definition: rcu.h:195