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