Ardour  9.0-pre0-582-g084a23a80d
signals.h
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2009-2016 Paul Davis <paul@linuxaudiosystems.com>
3  * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
4  * Copyright (C) 2013 John Emmas <john@creativepost.co.uk>
5  * Copyright (C) 2015-2017 Robin Gareus <robin@gareus.org>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21 
22 #pragma once
23 
24 #include <csignal>
25 
26 #include <list>
27 #include <map>
28 
29 #ifdef nil
30 #undef nil
31 #endif
32 
33 #include <atomic>
34 
35 #include <glibmm/threads.h>
36 
37 #include <boost/bind/protect.hpp>
38 #include <boost/enable_shared_from_this.hpp>
39 #include <optional>
40 
41 #include "pbd/libpbd_visibility.h"
42 #include "pbd/event_loop.h"
43 
44 #ifndef NDEBUG
45 #define DEBUG_PBD_SIGNAL_CONNECTIONS
46 #endif
47 
48 #ifdef DEBUG_PBD_SIGNAL_CONNECTIONS
49 #include "pbd/stacktrace.h"
50 #include <iostream>
51 #endif
52 
53 using namespace std::placeholders;
54 
55 namespace PBD {
56 
57 class LIBPBD_API Connection;
58 
59 class LIBPBD_API ScopedConnection;
60 
61 class LIBPBD_API ScopedConnectionList;
62 
64 {
65 public:
67  : _in_dtor (false)
69  , _debug_connection (false)
70 #endif
71  {}
72  virtual ~SignalBase () { }
73  virtual void disconnect (std::shared_ptr<Connection>) = 0;
74 #ifdef DEBUG_PBD_SIGNAL_CONNECTIONS
75  void set_debug_connection (bool yn) { _debug_connection = yn; }
76 #endif
77 
78 protected:
79  mutable Glib::Threads::Mutex _mutex;
80  std::atomic<bool> _in_dtor;
81 #ifdef DEBUG_PBD_SIGNAL_CONNECTIONS
83 #endif
84 };
85 
86 template<typename R>
87 class /*LIBPBD_API*/ OptionalLastValue
88 {
89 public:
90  typedef std::optional<R> result_type;
91 
92  template <typename Iter>
93  result_type operator() (Iter first, Iter last) const {
94  result_type r;
95  while (first != last) {
96  r = *first;
97  ++first;
98  }
99 
100  return r;
101  }
102 };
103 
104 template <typename Combiner, typename _Signature>
106 
107 template <typename Combiner, typename R, typename... A>
108 class SignalWithCombiner<Combiner, R(A...)> : public SignalBase
109 {
110 public:
111 
112  typedef std::function<R(A...)> slot_function_type;
113 
114 private:
115 
117  typedef std::map<std::shared_ptr<Connection>, slot_function_type> Slots;
119 
120 public:
121 
122  static void compositor (typename std::function<void(A...)> f,
123  EventLoop* event_loop,
124  EventLoop::InvalidationRecord* ir, A... a);
125 
126  ~SignalWithCombiner ();
127 
128  void connect_same_thread (ScopedConnection& c, const slot_function_type& slot);
129  void connect_same_thread (ScopedConnectionList& clist, const slot_function_type& slot);
130  void connect (ScopedConnectionList& clist,
132  const slot_function_type& slot,
133  PBD::EventLoop* event_loop);
134  void connect (ScopedConnection& c,
136  const slot_function_type& slot,
137  PBD::EventLoop* event_loop);
138 
143  typename std::conditional_t<std::is_void_v<R>, R, typename Combiner::result_type>
144  operator() (A... a);
145 
146  bool empty () const {
147  Glib::Threads::Mutex::Lock lm (_mutex);
148  return _slots.empty ();
149  }
150 
151  size_t size () const {
152  Glib::Threads::Mutex::Lock lm (_mutex);
153  return _slots.size ();
154  }
155 
156 private:
157 
158  friend class Connection;
159 
160  std::shared_ptr<Connection> _connect (PBD::EventLoop::InvalidationRecord* ir, slot_function_type f);
161  void disconnect (std::shared_ptr<Connection> c);
162 
163 };
164 
165 template <typename R>
167 
168 template <typename _Signature>
169 class Signal;
170 
171 template <typename R, typename... A>
172 class Signal<R(A...)> : public SignalWithCombiner<DefaultCombiner<R>, R(A...)> {};
173 
174 class LIBPBD_API Connection : public std::enable_shared_from_this<Connection>
175 {
176 public:
178  : _signal (b)
179  , _invalidation_record (ir)
180  {
181  if (_invalidation_record) {
182  _invalidation_record->ref ();
183  }
184  }
185 
186  void disconnect ()
187  {
188  Glib::Threads::Mutex::Lock lm (_mutex);
189  SignalBase* signal = _signal.exchange (0, std::memory_order_acq_rel);
190  if (signal) {
191  /* It is safe to assume that signal has not been destructed.
192  * If ~Signal d'tor runs, it will call our signal_going_away()
193  * which will block until we're done here.
194  *
195  * This will lock Signal::_mutex, and call disconnected ()
196  * or return immediately if Signal is being destructed.
197  */
198  signal->disconnect (shared_from_this ());
199  }
200  }
201 
202  void disconnected ()
203  {
204  if (_invalidation_record) {
205  _invalidation_record->unref ();
206  }
207  }
208 
210  {
211  /* called with Signal::_mutex held */
212  if (!_signal.exchange (0, std::memory_order_acq_rel)) {
213  /* disconnect () grabbed the signal, but signal->disconnect()
214  * has not [yet] removed the entry from the list.
215  *
216  * Allow disconnect () to complete, which will
217  * be an effective NO-OP since SignalBase::_in_dtor is true,
218  * then we can proceed.
219  */
220  Glib::Threads::Mutex::Lock lm (_mutex);
221  }
222  if (_invalidation_record) {
223  _invalidation_record->unref ();
224  }
225  }
226 
227 private:
228  Glib::Threads::Mutex _mutex;
229  std::atomic<SignalBase*> _signal;
231 };
232 
233 typedef std::shared_ptr<Connection> UnscopedConnection;
234 
236 {
237 public:
241  disconnect ();
242  }
243 
244  void disconnect ()
245  {
246  if (_c) {
247  _c->disconnect ();
248  }
249  }
250 
251  ScopedConnection& operator= (UnscopedConnection const & o)
252  {
253  if (_c == o) {
254  return *this;
255  }
256 
257  disconnect ();
258  _c = o;
259  return *this;
260  }
261 
262  UnscopedConnection const & the_connection() const { return _c; }
263 
264 private:
266 };
267 
269 {
270  public:
273  ScopedConnectionList& operator= (const ScopedConnectionList&) = delete;
275 
278 
279  private:
280  /* Even though our signals code is thread-safe, this additional list of
281  scoped connections needs to be protected in 2 cases:
282 
283  (1) (unlikely) we make a connection involving a callback on the
284  same object from 2 threads. (wouldn't that just be appalling
285  programming style?)
286 
287  (2) where we are dropping connections in one thread and adding
288  one from another.
289  */
290 
291  Glib::Threads::Mutex _scoped_connection_lock;
292 
293  typedef std::list<ScopedConnection*> ConnectionList;
295 };
296 
297 template <typename Combiner, typename R, typename... A>
298 void
299 SignalWithCombiner<Combiner, R(A...)>::compositor (typename std::function<void(A...)> f,
300  EventLoop* event_loop,
301  EventLoop::InvalidationRecord* ir, A... a)
302 {
303  event_loop->call_slot (ir, std::bind (f, a...));
304 }
305 
306 template <typename Combiner, typename R, typename... A>
307 SignalWithCombiner<Combiner, R(A...)>::~SignalWithCombiner ()
308 {
309  _in_dtor.store (true, std::memory_order_release);
310  Glib::Threads::Mutex::Lock lm (_mutex);
311  /* Tell our connection objects that we are going away, so they don't try to call us */
312  for (typename Slots::const_iterator i = _slots.begin(); i != _slots.end(); ++i) {
313  i->first->signal_going_away ();
314  }
315 }
316 
324 template <typename Combiner, typename R, typename... A>
325 void
326 SignalWithCombiner<Combiner, R(A...)>::connect_same_thread (ScopedConnection& c,
327  const slot_function_type& slot)
328 {
329  c = _connect (0, slot);
330 }
331 
339 template <typename Combiner, typename R, typename... A>
340 void
341 SignalWithCombiner<Combiner, R(A...)>::connect_same_thread (ScopedConnectionList& clist,
342  const slot_function_type& slot)
343 {
344  clist.add_connection (_connect (0, slot));
345 }
346 
372 template <typename Combiner, typename R, typename... A>
373 void
374 SignalWithCombiner<Combiner, R(A...)>::connect (ScopedConnectionList& clist,
376  const slot_function_type& slot,
377  PBD::EventLoop* event_loop)
378 {
379  if (ir) {
380  ir->event_loop = event_loop;
381  }
382 
383  clist.add_connection (_connect (ir, [slot, event_loop, ir](A... a) {
384  return compositor(slot, event_loop, ir, a...);
385  }));
386 }
387 
393 template <typename Combiner, typename R, typename... A>
394 void
395 SignalWithCombiner<Combiner, R(A...)>::connect (ScopedConnection& c,
397  const slot_function_type& slot,
398  PBD::EventLoop* event_loop)
399 {
400  if (ir) {
401  ir->event_loop = event_loop;
402  }
403 
404  c = _connect (ir, [slot, event_loop, ir](A... a) {
405  return compositor(slot, event_loop, ir, a...);
406  });
407 }
408 
414 template <typename Combiner, typename R, typename... A>
415 typename std::conditional_t<std::is_void_v<R>, R, typename Combiner::result_type>
416 SignalWithCombiner<Combiner, R(A...)>::operator() (A... a)
417 {
418  /* First, take a copy of our list of slots as it is now */
419 
420  Slots s;
421  {
422  Glib::Threads::Mutex::Lock lm (_mutex);
423  s = _slots;
424  }
425 
426  if constexpr (std::is_void_v<R>) {
427  for (typename Slots::const_iterator i = s.begin(); i != s.end(); ++i) {
428 
429  /* We may have just called a slot, and this may have resulted in
430  * disconnection of other slots from us. The list copy means that
431  * this won't cause any problems with invalidated iterators, but we
432  * must check to see if the slot we are about to call is still on the list.
433  */
434  bool still_there = false;
435  {
436  Glib::Threads::Mutex::Lock lm (_mutex);
437  still_there = _slots.find (i->first) != _slots.end ();
438  }
439 
440  if (still_there) {
441  (i->second)(a...);
442  }
443  }
444  } else {
445  std::list<R> r;
446  for (typename Slots::const_iterator i = s.begin(); i != s.end(); ++i) {
447 
448  /* We may have just called a slot, and this may have resulted in
449  * disconnection of other slots from us. The list copy means that
450  * this won't cause any problems with invalidated iterators, but we
451  * must check to see if the slot we are about to call is still on the list.
452  */
453  bool still_there = false;
454  {
455  Glib::Threads::Mutex::Lock lm (_mutex);
456  still_there = _slots.find (i->first) != _slots.end ();
457  }
458 
459  if (still_there) {
460  r.push_back ((i->second)(a...));
461  }
462  }
463 
464  /* Call our combiner to do whatever is required to the result values */
465  Combiner c;
466  return c (r.begin(), r.end());
467  }
468 }
469 
470 template <typename Combiner, typename R, typename... A>
471 std::shared_ptr<Connection>
472 SignalWithCombiner<Combiner, R(A...)>::_connect (PBD::EventLoop::InvalidationRecord* ir,
474 {
475  std::shared_ptr<Connection> c (new Connection (this, ir));
476  Glib::Threads::Mutex::Lock lm (_mutex);
477  _slots[c] = f;
478  #ifdef DEBUG_PBD_SIGNAL_CONNECTIONS
479  if (_debug_connection) {
480  std::cerr << "+++++++ CONNECT " << this << " size now " << _slots.size() << std::endl;
481  stacktrace (std::cerr, 10);
482  }
483  #endif
484  return c;
485 }
486 
487 template <typename Combiner, typename R, typename... A>
488 void
489 SignalWithCombiner<Combiner, R(A...)>::disconnect (std::shared_ptr<Connection> c)
490 {
491  /* ~ScopedConnection can call this concurrently with our d'tor */
492  Glib::Threads::Mutex::Lock lm (_mutex, Glib::Threads::TRY_LOCK);
493  while (!lm.locked()) {
494  if (_in_dtor.load (std::memory_order_acquire)) {
495  /* d'tor signal_going_away() took care of everything already */
496  return;
497  }
498  /* Spin */
499  lm.try_acquire ();
500  }
501  _slots.erase (c);
502  lm.release ();
503 
504  c->disconnected ();
505  #ifdef DEBUG_PBD_SIGNAL_CONNECTIONS
506  if (_debug_connection) {
507  std::cerr << "------- DISCCONNECT " << this << " size now " << _slots.size() << std::endl;
508  stacktrace (std::cerr, 10);
509  }
510  #endif
511 }
512 
513 } /* namespace */
514 
void signal_going_away()
Definition: signals.h:209
void disconnect()
Definition: signals.h:186
std::atomic< SignalBase * > _signal
Definition: signals.h:229
Glib::Threads::Mutex _mutex
Definition: signals.h:228
PBD::EventLoop::InvalidationRecord * _invalidation_record
Definition: signals.h:230
void disconnected()
Definition: signals.h:202
Connection(SignalBase *b, PBD::EventLoop::InvalidationRecord *ir)
Definition: signals.h:177
virtual bool call_slot(InvalidationRecord *, const std::function< void()> &)=0
std::optional< R > result_type
Definition: signals.h:90
Glib::Threads::Mutex _scoped_connection_lock
Definition: signals.h:291
void add_connection(const UnscopedConnection &c)
ConnectionList _scoped_connection_list
Definition: signals.h:294
std::list< ScopedConnection * > ConnectionList
Definition: signals.h:293
ScopedConnectionList(const ScopedConnectionList &)=delete
ScopedConnection(UnscopedConnection c)
Definition: signals.h:239
UnscopedConnection _c
Definition: signals.h:265
UnscopedConnection const & the_connection() const
Definition: signals.h:262
std::atomic< bool > _in_dtor
Definition: signals.h:80
virtual void disconnect(std::shared_ptr< Connection >)=0
virtual ~SignalBase()
Definition: signals.h:72
void set_debug_connection(bool yn)
Definition: signals.h:75
bool _debug_connection
Definition: signals.h:82
Glib::Threads::Mutex _mutex
Definition: signals.h:79
std::map< std::shared_ptr< Connection >, slot_function_type > Slots
Definition: signals.h:117
std::function< R(A...)> slot_function_type
Definition: signals.h:112
#define LIBPBD_API
Definition: axis_view.h:42
std::shared_ptr< Connection > UnscopedConnection
Definition: signals.h:233
void stacktrace(std::ostream &out, int levels=0, size_t start_level=0)
#define DEBUG_PBD_SIGNAL_CONNECTIONS
Definition: signals.h:45