use a unique key to store per-thread request buffers
[ardour.git] / libs / pbd / event_loop.cc
1 /*
2     Copyright (C) 2012 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <cstring>
21
22 #include <pthread.h>
23
24 #include "pbd/compose.h"
25 #include "pbd/debug.h"
26 #include "pbd/event_loop.h"
27 #include "pbd/error.h"
28 #include "pbd/stacktrace.h"
29
30 #include "i18n.h"
31
32 using namespace PBD;
33 using namespace std;
34
35 static void do_not_delete_the_loop_pointer (void*) { }
36
37 Glib::Threads::Private<EventLoop> EventLoop::thread_event_loop (do_not_delete_the_loop_pointer);
38
39 Glib::Threads::RWLock EventLoop::thread_buffer_requests_lock;
40 EventLoop::ThreadRequestBufferList EventLoop::thread_buffer_requests;
41 EventLoop::RequestBufferSuppliers EventLoop::request_buffer_suppliers;
42
43 EventLoop::EventLoop (string const& name)
44         : _name (name)
45 {
46 }
47
48 EventLoop*
49 EventLoop::get_event_loop_for_thread()
50 {
51         return thread_event_loop.get ();
52 }
53
54 void
55 EventLoop::set_event_loop_for_thread (EventLoop* loop)
56 {
57         thread_event_loop.set (loop);
58 }
59
60 void*
61 EventLoop::invalidate_request (void* data)
62 {
63         InvalidationRecord* ir = (InvalidationRecord*) data;
64
65         /* Some of the requests queued with an EventLoop may involve functors
66          * that make method calls to objects whose lifetime is shorter
67          * than the EventLoop's. We do not want to make those calls if the
68          * object involve has been destroyed. To prevent this, we
69          * provide a way to invalidate those requests when the object is
70          * destroyed.
71          *
72          * An object was passed to __invalidator() which added a callback to
73          * EventLoop::invalidate_request() to its "notify when destroyed"
74          * list. __invalidator() returned an InvalidationRecord that has been
75          * to passed to this function as data.
76          *
77          * The object is currently being destroyed and so we want to
78          * mark all requests involving this object that are queued with
79          * any EventLoop as invalid.
80          *
81          * As of April 2012, we are usign sigc::trackable as the base object
82          * used to queue calls to ::invalidate_request() to be made upon
83          * destruction, via its ::add_destroy_notify_callback() API. This is
84          * not necessarily ideal, but it is very close to precisely what we
85          * want, and many of the objects we want to do this with already
86          * inherit (indirectly) from sigc::trackable.
87          */
88
89         if (ir->event_loop) {
90                 Glib::Threads::Mutex::Lock lm (ir->event_loop->slot_invalidation_mutex());
91                 for (list<BaseRequestObject*>::iterator i = ir->requests.begin(); i != ir->requests.end(); ++i) {
92                         (*i)->valid = false;
93                         (*i)->invalidation = 0;
94                 }
95                 delete ir;
96         }
97
98         return 0;
99 }
100
101 vector<EventLoop::ThreadBufferMapping>
102 EventLoop::get_request_buffers_for_target_thread (const std::string& target_thread)
103 {
104         vector<ThreadBufferMapping> ret;
105         Glib::Threads::RWLock::WriterLock lm (thread_buffer_requests_lock);
106
107         for (ThreadRequestBufferList::const_iterator x = thread_buffer_requests.begin();
108              x != thread_buffer_requests.end(); ++x) {
109
110                 if (x->second.target_thread_name == target_thread) {
111                         ret.push_back (x->second);
112                 }
113         }
114
115         DEBUG_TRACE (PBD::DEBUG::EventLoop, string_compose ("for thread \"%1\", found %2 request buffers\n", target_thread, ret.size()));
116
117         return ret;
118 }
119
120 void
121 EventLoop::register_request_buffer_factory (const string& target_thread_name,
122                                             void* (*factory)(uint32_t))
123 {
124
125         RequestBufferSupplier trs;
126         trs.name = target_thread_name;
127         trs.factory = factory;
128
129         {
130                 Glib::Threads::RWLock::WriterLock lm (thread_buffer_requests_lock);
131                 request_buffer_suppliers.push_back (trs);
132         }
133 }
134
135 void
136 EventLoop::pre_register (const string& emitting_thread_name, uint32_t num_requests)
137 {
138         /* Threads that need to emit signals "towards" other threads, but with
139            RT safe behavior may be created before the receiving threads
140            exist. This makes it impossible for them to use the
141            ThreadCreatedWithRequestSize signal to notify receiving threads of
142            their existence.
143
144            This function creates a request buffer for them to use with
145            the (not yet) created threads, and stores it where the receiving
146            thread can find it later.
147          */
148
149         ThreadBufferMapping mapping;
150         Glib::Threads::RWLock::ReaderLock lm (thread_buffer_requests_lock);
151
152         for (RequestBufferSuppliers::iterator trs = request_buffer_suppliers.begin(); trs != request_buffer_suppliers.end(); ++trs) {
153
154                 if (!trs->factory) {
155                         /* no factory - no request buffer required or expected */
156                         continue;
157                 }
158
159                 if (emitting_thread_name == trs->name) {
160                         /* no need to register an emitter with itself */
161                         continue;
162                 }
163
164                 mapping.emitting_thread = pthread_self();
165                 mapping.target_thread_name = trs->name;
166
167                 /* Allocate a suitably sized request buffer. This will set the
168                  * thread-local variable that holds a pointer to this request
169                  * buffer.
170                  */
171                 mapping.request_buffer = trs->factory (num_requests);
172
173                 /* now store it where the receiving thread (trs->name) can find
174                    it if and when it is created. (Discovery happens in the
175                    AbstractUI constructor. Note that if
176                 */
177
178                 const string key = string_compose ("%1/%2", mapping.emitting_thread, mapping.target_thread_name);
179
180                 /* note that there is no cleanup mechanism to remove
181                  * dead/out-of-date entries from this map.
182                  *
183                  * the request buffers themselves will be cleaned up
184                  * when the requesting thread exits (by the
185                  * thread-local-storage (TLS) cleanup mechanism). 
186                  *
187                  * but an entry will remain in the map.
188                  *
189                  * really need a way to register some end-of-thread callback
190                  * that will remove the entry from the thread_buffer_requests
191                  * container. but there is no such thing in the pthreads API
192                  *
193                  * the target thread only searches the map once, when the event
194                  * loop object is constructed. if it finds invalid buffers
195                  * it will (a) never get any requests for them anyway (b) will
196                  * find them marked "dead" and delete them.
197                  */
198
199                 thread_buffer_requests[key] = mapping;
200                 DEBUG_TRACE (PBD::DEBUG::EventLoop, string_compose ("pre-registered request buffer for \"%1\" to send to \"%2\", buffer @ %3 (key was %4)\n",
201                                                                     emitting_thread_name, trs->name, mapping.request_buffer, key));
202         }
203 }
204