Ardour  9.0-pre0-1159-gcb8dd39f31
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 #include "pbd/stack_allocator.h"
44 
45 #ifndef NDEBUG
46 #define DEBUG_PBD_SIGNAL_CONNECTIONS
47 #define DEBUG_PBD_SIGNAL_EMISSION
48 #endif
49 
50 #ifdef DEBUG_PBD_SIGNAL_CONNECTIONS
51 #include "pbd/stacktrace.h"
52 #include <iostream>
53 #endif
54 
55 
56 using namespace std::placeholders;
57 
58 namespace PBD {
59 
60 #ifdef DEBUG_PBD_SIGNAL_CONNECTIONS
61 static std::size_t max_signal_subscribers;
62 #endif
63 
65 
67 
69 
71 {
72 public:
74  : _in_dtor (false)
76  , _debug_connection (false)
77 #endif
79  , _debug_emission (false)
80 #endif
81  {}
82  virtual ~SignalBase () { }
83  virtual void disconnect (std::shared_ptr<Connection>) = 0;
84 #ifdef DEBUG_PBD_SIGNAL_CONNECTIONS
85  void set_debug_connection (bool yn) { _debug_connection = yn; }
86 #endif
87 #ifdef DEBUG_PBD_SIGNAL_EMISSION
88  void set_debug_emission (bool yn) { _debug_emission = yn; }
89 #endif
90 
91 protected:
92  mutable Glib::Threads::Mutex _mutex;
93  std::atomic<bool> _in_dtor;
94 #ifdef DEBUG_PBD_SIGNAL_CONNECTIONS
96 #endif
97 #ifdef DEBUG_PBD_SIGNAL_EMISSION
99 #endif
100 };
101 
102 template<typename R>
103 class /*LIBPBD_API*/ OptionalLastValue
104 {
105 public:
106  typedef std::optional<R> result_type;
107 
108  template <typename Iter>
109  result_type operator() (Iter first, Iter last) const {
110  result_type r;
111  while (first != last) {
112  r = *first;
113  ++first;
114  }
115 
116  return r;
117  }
118 };
119 
120 template <typename Combiner, typename _Signature>
122 
123 template <typename Combiner, typename R, typename... A>
124 class SignalWithCombiner<Combiner, R(A...)> : public SignalBase
125 {
126 public:
127 
128  typedef std::function<R(A...)> slot_function_type;
129 
130 private:
131 
133  typedef std::map<std::shared_ptr<Connection>, slot_function_type> Slots;
135 
136 public:
137 
138  static void compositor (typename std::function<void(A...)> f,
139  EventLoop* event_loop,
140  EventLoop::InvalidationRecord* ir, A... a);
141 
142  ~SignalWithCombiner ();
143 
144  void connect_same_thread (ScopedConnection& c, const slot_function_type& slot);
145  void connect_same_thread (ScopedConnectionList& clist, const slot_function_type& slot);
146  void connect (ScopedConnectionList& clist,
148  const slot_function_type& slot,
149  PBD::EventLoop* event_loop);
150  void connect (ScopedConnection& c,
152  const slot_function_type& slot,
153  PBD::EventLoop* event_loop);
154 
159  typename std::conditional_t<std::is_void_v<R>, R, typename Combiner::result_type>
160  operator() (A... a);
161 
162  bool empty () const {
163  Glib::Threads::Mutex::Lock lm (_mutex);
164  return _slots.empty ();
165  }
166 
167  size_t size () const {
168  Glib::Threads::Mutex::Lock lm (_mutex);
169  return _slots.size ();
170  }
171 
172 private:
173 
174  friend class Connection;
175 
176  std::shared_ptr<Connection> _connect (PBD::EventLoop::InvalidationRecord* ir, slot_function_type f);
177  void disconnect (std::shared_ptr<Connection> c);
178 
179 };
180 
181 template <typename R>
183 
184 template <typename _Signature>
185 class Signal;
186 
187 template <typename R, typename... A>
188 class Signal<R(A...)> : public SignalWithCombiner<DefaultCombiner<R>, R(A...)> {};
189 
190 class LIBPBD_API Connection : public std::enable_shared_from_this<Connection>
191 {
192 public:
194  : _signal (b)
195  , _invalidation_record (ir)
196  {
197  if (_invalidation_record) {
198  _invalidation_record->ref ();
199  }
200  }
201 
202  void disconnect ()
203  {
204  Glib::Threads::Mutex::Lock lm (_mutex);
205  SignalBase* signal = _signal.exchange (0, std::memory_order_acq_rel);
206  if (signal) {
207  /* It is safe to assume that signal has not been destructed.
208  * If ~Signal d'tor runs, it will call our signal_going_away()
209  * which will block until we're done here.
210  *
211  * This will lock Signal::_mutex, and call disconnected ()
212  * or return immediately if Signal is being destructed.
213  */
214  signal->disconnect (shared_from_this ());
215  }
216  }
217 
218  void disconnected ()
219  {
220  if (_invalidation_record) {
221  _invalidation_record->unref ();
222  }
223  }
224 
226  {
227  /* called with Signal::_mutex held */
228  if (!_signal.exchange (0, std::memory_order_acq_rel)) {
229  /* disconnect () grabbed the signal, but signal->disconnect()
230  * has not [yet] removed the entry from the list.
231  *
232  * Allow disconnect () to complete, which will
233  * be an effective NO-OP since SignalBase::_in_dtor is true,
234  * then we can proceed.
235  */
236  Glib::Threads::Mutex::Lock lm (_mutex);
237  }
238  if (_invalidation_record) {
239  _invalidation_record->unref ();
240  }
241  }
242 
243 private:
244  Glib::Threads::Mutex _mutex;
245  std::atomic<SignalBase*> _signal;
247 };
248 
249 typedef std::shared_ptr<Connection> UnscopedConnection;
250 
252 {
253 public:
257  disconnect ();
258  }
259 
260  void disconnect ()
261  {
262  if (_c) {
263  _c->disconnect ();
264  }
265  }
266 
267  ScopedConnection& operator= (UnscopedConnection const & o)
268  {
269  if (_c == o) {
270  return *this;
271  }
272 
273  disconnect ();
274  _c = o;
275  return *this;
276  }
277 
278  UnscopedConnection const & the_connection() const { return _c; }
279 
280 private:
282 };
283 
285 {
286  public:
289  ScopedConnectionList& operator= (const ScopedConnectionList&) = delete;
291 
294 
295  std::list<ScopedConnectionList*>::size_type size() const { Glib::Threads::Mutex::Lock lm (_scoped_connection_lock); return _scoped_connection_list.size(); }
296 
297  private:
298  /* Even though our signals code is thread-safe, this additional list of
299  scoped connections needs to be protected in 2 cases:
300 
301  (1) (unlikely) we make a connection involving a callback on the
302  same object from 2 threads. (wouldn't that just be appalling
303  programming style?)
304 
305  (2) where we are dropping connections in one thread and adding
306  one from another.
307  */
308 
309  mutable Glib::Threads::Mutex _scoped_connection_lock;
310 
311  typedef std::list<ScopedConnection*> ConnectionList;
313 };
314 
315 template <typename Combiner, typename R, typename... A>
316 void
317 SignalWithCombiner<Combiner, R(A...)>::compositor (typename std::function<void(A...)> f,
318  EventLoop* event_loop,
319  EventLoop::InvalidationRecord* ir, A... a)
320 {
321  event_loop->call_slot (ir, std::bind (f, a...));
322 }
323 
324 template <typename Combiner, typename R, typename... A>
325 SignalWithCombiner<Combiner, R(A...)>::~SignalWithCombiner ()
326 {
327  _in_dtor.store (true, std::memory_order_release);
328  Glib::Threads::Mutex::Lock lm (_mutex);
329  /* Tell our connection objects that we are going away, so they don't try to call us */
330  for (typename Slots::const_iterator i = _slots.begin(); i != _slots.end(); ++i) {
331  i->first->signal_going_away ();
332  }
333 }
334 
342 template <typename Combiner, typename R, typename... A>
343 void
344 SignalWithCombiner<Combiner, R(A...)>::connect_same_thread (ScopedConnection& c,
345  const slot_function_type& slot)
346 {
347  c = _connect (0, slot);
348 }
349 
357 template <typename Combiner, typename R, typename... A>
358 void
359 SignalWithCombiner<Combiner, R(A...)>::connect_same_thread (ScopedConnectionList& clist,
360  const slot_function_type& slot)
361 {
362  clist.add_connection (_connect (0, slot));
363 }
364 
390 template <typename Combiner, typename R, typename... A>
391 void
392 SignalWithCombiner<Combiner, R(A...)>::connect (ScopedConnectionList& clist,
394  const slot_function_type& slot,
395  PBD::EventLoop* event_loop)
396 {
397  if (ir) {
398  ir->event_loop = event_loop;
399  }
400 
401  clist.add_connection (_connect (ir, [slot, event_loop, ir](A... a) {
402  return compositor(slot, event_loop, ir, a...);
403  }));
404 }
405 
411 template <typename Combiner, typename R, typename... A>
412 void
413 SignalWithCombiner<Combiner, R(A...)>::connect (ScopedConnection& c,
415  const slot_function_type& slot,
416  PBD::EventLoop* event_loop)
417 {
418  if (ir) {
419  ir->event_loop = event_loop;
420  }
421 
422  c = _connect (ir, [slot, event_loop, ir](A... a) {
423  return compositor(slot, event_loop, ir, a...);
424  });
425 }
426 
432 template <typename Combiner, typename R, typename... A>
433 typename std::conditional_t<std::is_void_v<R>, R, typename Combiner::result_type>
434 SignalWithCombiner<Combiner, R(A...)>::operator() (A... a)
435 {
436 #ifdef DEBUG_PBD_SIGNAL_EMISSION
437  if (_debug_emission) {
438  std::cerr << "------ Signal @ " << this << " emission process begins\n";
439  PBD::stacktrace (std::cerr, 19);
440  }
441 #endif
442  const std::size_t nslots = 512;
443  std::vector<Connection*,PBD::StackAllocator<Connection*,nslots> > s;
444 
445  /* First, make a copy of the current connection state for us to iterate
446  * over later (the connection state may be changed by a signal handler.
447  */
448 
449  {
450  Glib::Threads::Mutex::Lock lm (_mutex);
451  /* copy only the raw pointer, no need for a shared_ptr in this
452  * context, we only use the address as a lookup into the _slots
453  * container. Note: because of the use of a stack allocator,
454  * this is *unlikely* to cause any (heap) memory
455  * allocation. That will only happen if the number of
456  * connections to this signal exceeds the value of nslots
457  * defined above. As of April 2025, the maximum number of
458  * connections appears to be ntracks+1.
459  */
460  for (auto const & [connection,functor] : _slots) {
461  s.push_back (connection.get());
462  }
463  }
464 
465  if constexpr (std::is_void_v<R>) {
466  slot_function_type functor;
467 
468  for (auto const & c : s) {
469 
470  /* We may have just called a slot, and this may have
471  * resulted in disconnection of other slots from us.
472  * The list copy means that this won't cause any
473  * problems with invalidated iterators, but we must
474  * check to see if the slot we are about to call is
475  * still on the list.
476  */
477  bool still_there = false;
478 
479  {
480  Glib::Threads::Mutex::Lock lm (_mutex);
481  typename Slots::const_iterator f = std::find_if (_slots.begin(), _slots.end(), [&](typename Slots::value_type const & elem) { return elem.first.get() == c; });
482  if (f != _slots.end()) {
483  functor = f->second;
484  still_there = true;
485  }
486  }
487 
488  if (still_there) {
489 #ifdef DEBUG_PBD_SIGNAL_EMISSION
490  if (_debug_emission) {
491  std::cerr << "signal @ " << this << " calling slot for connection @ " << c << " of " << _slots.size() << std::endl;
492  }
493 #endif
494  functor (a...);
495  }
496  }
497 
498 #ifdef DEBUG_PBD_SIGNAL_EMISSION
499  if (_debug_emission) {
500  std::cerr << "------ Signal @ " << this << " emission process ends\n";
501  }
502 #endif
503  return;
504 
505  } else {
506  std::list<R,PBD::StackAllocator<R,nslots> > r;
507  slot_function_type functor;
508 
509  for (auto const & c : s) {
510 
511  /* We may have just called a slot, and this may have resulted in
512  * disconnection of other slots from us. The list copy means that
513  * this won't cause any problems with invalidated iterators, but we
514  * must check to see if the slot we are about to call is still on the list.
515  */
516  bool still_there = false;
517 
518  {
519  Glib::Threads::Mutex::Lock lm (_mutex);
520  typename Slots::const_iterator f = std::find_if (_slots.begin(), _slots.end(), [&](typename Slots::value_type const & elem) { return elem.first.get() == c; });
521 
522  if (f != _slots.end()) {
523  functor = f->second;
524  still_there = true;
525  }
526  }
527  if (still_there) {
528 #ifdef DEBUG_PBD_SIGNAL_EMISSION
529  if (_debug_emission) {
530  std::cerr << "signal @ " << this << " calling non-void slot for connection @ " << c << " of " << _slots.size() << std::endl;
531  }
532 #endif
533  r.push_back (functor (a...));
534  }
535 
536 #ifdef DEBUG_PBD_SIGNAL_EMISSION
537  if (_debug_emission) {
538  std::cerr << "------ Signal @ " << this << " emission process ends\n";
539  }
540 #endif
541  }
542  /* Call our combiner to do whatever is required to the result values */
543  Combiner c;
544  return c (r.begin(), r.end());
545  }
546 }
547 
548 template <typename Combiner, typename R, typename... A>
549 std::shared_ptr<Connection>
550 SignalWithCombiner<Combiner, R(A...)>::_connect (PBD::EventLoop::InvalidationRecord* ir,
552 {
553  std::shared_ptr<Connection> c (new Connection (this, ir));
554  Glib::Threads::Mutex::Lock lm (_mutex);
555  _slots[c] = f;
556 
557 #ifdef DEBUG_PBD_SIGNAL_CONNECTIONS
558  if (_slots.size() > max_signal_subscribers) {
559  max_signal_subscribers = _slots.size();
560  }
561  if (_debug_connection) {
562  std::cerr << "+++++++ CONNECT " << this << " via connection @ " << c << " size now " << _slots.size() << std::endl;
563  stacktrace (std::cerr, 10);
564  }
565 #endif
566  return c;
567 }
568 
569 template <typename Combiner, typename R, typename... A>
570 void
571 SignalWithCombiner<Combiner, R(A...)>::disconnect (std::shared_ptr<Connection> c)
572 {
573  /* ~ScopedConnection can call this concurrently with our d'tor */
574  Glib::Threads::Mutex::Lock lm (_mutex, Glib::Threads::TRY_LOCK);
575  while (!lm.locked()) {
576  if (_in_dtor.load (std::memory_order_acquire)) {
577  /* d'tor signal_going_away() took care of everything already */
578  return;
579  }
580  /* Spin */
581  lm.try_acquire ();
582  }
583  _slots.erase (c);
584  lm.release ();
585 
586  c->disconnected ();
587  #ifdef DEBUG_PBD_SIGNAL_CONNECTIONS
588  if (_debug_connection) {
589  std::cerr << "------- DISCCONNECT " << this << " size now " << _slots.size() << std::endl;
590  stacktrace (std::cerr, 10);
591  }
592  #endif
593 }
594 
595 } /* namespace */
void signal_going_away()
Definition: signals.h:225
void disconnect()
Definition: signals.h:202
std::atomic< SignalBase * > _signal
Definition: signals.h:245
Glib::Threads::Mutex _mutex
Definition: signals.h:244
PBD::EventLoop::InvalidationRecord * _invalidation_record
Definition: signals.h:246
void disconnected()
Definition: signals.h:218
Connection(SignalBase *b, PBD::EventLoop::InvalidationRecord *ir)
Definition: signals.h:193
virtual bool call_slot(InvalidationRecord *, const std::function< void()> &)=0
std::optional< R > result_type
Definition: signals.h:106
Glib::Threads::Mutex _scoped_connection_lock
Definition: signals.h:309
std::list< ScopedConnectionList * >::size_type size() const
Definition: signals.h:295
void add_connection(const UnscopedConnection &c)
ConnectionList _scoped_connection_list
Definition: signals.h:312
std::list< ScopedConnection * > ConnectionList
Definition: signals.h:311
ScopedConnectionList(const ScopedConnectionList &)=delete
ScopedConnection(UnscopedConnection c)
Definition: signals.h:255
UnscopedConnection _c
Definition: signals.h:281
UnscopedConnection const & the_connection() const
Definition: signals.h:278
std::atomic< bool > _in_dtor
Definition: signals.h:93
virtual void disconnect(std::shared_ptr< Connection >)=0
virtual ~SignalBase()
Definition: signals.h:82
void set_debug_connection(bool yn)
Definition: signals.h:85
bool _debug_emission
Definition: signals.h:98
bool _debug_connection
Definition: signals.h:95
Glib::Threads::Mutex _mutex
Definition: signals.h:92
void set_debug_emission(bool yn)
Definition: signals.h:88
std::map< std::shared_ptr< Connection >, slot_function_type > Slots
Definition: signals.h:133
std::function< R(A...)> slot_function_type
Definition: signals.h:128
#define LIBPBD_API
Definition: axis_view.h:42
std::shared_ptr< Connection > UnscopedConnection
Definition: signals.h:249
static std::size_t max_signal_subscribers
Definition: signals.h:61
void stacktrace(std::ostream &out, int levels=0, size_t start_level=0)
#define DEBUG_PBD_SIGNAL_EMISSION
Definition: signals.h:47
#define DEBUG_PBD_SIGNAL_CONNECTIONS
Definition: signals.h:46