2 Copyright (C) 2012 Paul Davis
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.
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.
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.
23 #include "pbd/stacktrace.h"
24 #include "pbd/abstract_ui.h"
25 #include "pbd/pthread_utils.h"
26 #include "pbd/failed_constructor.h"
27 #include "pbd/debug.h"
33 template<typename RequestBuffer> void
34 cleanup_request_buffer (void* ptr)
36 RequestBuffer* rb = (RequestBuffer*) ptr;
38 /* there is the question of why we don't simply erase the request
39 * buffer and delete it right here, since we have to take the lock
42 * as of april 24th 2012, i don't have a good answer to that.
47 Glib::Threads::Mutex::Lock lm (rb->ui.request_buffer_map_lock);
53 Glib::Threads::Private<typename AbstractUI<R>::RequestBuffer> AbstractUI<R>::per_thread_request_buffer (cleanup_request_buffer<AbstractUI<R>::RequestBuffer>);
55 template <typename RequestObject>
56 AbstractUI<RequestObject>::AbstractUI (const string& name)
59 void (AbstractUI<RequestObject>::*pmf)(string,pthread_t,string,uint32_t) = &AbstractUI<RequestObject>::register_thread;
61 /* better to make this connect a handler that runs in the UI event loop but the syntax seems hard, and
62 register_thread() is thread safe anyway.
65 PBD::ThreadCreatedWithRequestSize.connect_same_thread (new_thread_connection, boost::bind (pmf, this, _1, _2, _3, _4));
68 template <typename RequestObject> void
69 AbstractUI<RequestObject>::register_thread (string target_gui, pthread_t thread_id, string /*thread name*/, uint32_t num_requests)
71 /* the calling thread wants to register with the thread that runs this
72 * UI's event loop, so that it will have its own per-thread queue of
73 * requests. this means that when it makes a request to this UI it can
74 * do so in a realtime-safe manner (no locks).
77 if (target_gui != name()) {
78 /* this UI is not the UI that the calling thread is trying to
84 /* the per_thread_request_buffer is a thread-private variable.
85 See pthreads documentation for more on these, but the key
86 thing is that it is a variable that as unique value for
87 each thread, guaranteed.
90 RequestBuffer* b = per_thread_request_buffer.get();
93 /* thread already registered with this UI
98 /* create a new request queue/ringbuffer */
100 b = new RequestBuffer (num_requests, *this);
103 /* add the new request queue (ringbuffer) to our map
104 so that we can iterate over it when the time is right.
105 This step is not RT-safe, but is assumed to be called
106 only at thread initialization time, not repeatedly,
107 and so this is of little consequence.
109 Glib::Threads::Mutex::Lock lm (request_buffer_map_lock);
110 request_buffers[thread_id] = b;
113 /* set this thread's per_thread_request_buffer to this new
114 queue/ringbuffer. remember that only this thread will
115 get this queue when it calls per_thread_request_buffer.get()
117 the second argument is a function that will be called
118 when the thread exits, and ensures that the buffer is marked
119 dead. it will then be deleted during a call to handle_ui_requests()
122 per_thread_request_buffer.set (b);
125 template <typename RequestObject> RequestObject*
126 AbstractUI<RequestObject>::get_request (RequestType rt)
128 RequestBuffer* rbuf = per_thread_request_buffer.get ();
129 RequestBufferVector vec;
131 /* see comments in ::register_thread() above for an explanation of
132 the per_thread_request_buffer variable
137 /* the calling thread has registered with this UI and therefore
138 * we have a per-thread request queue/ringbuffer. use it. this
139 * "allocation" of a request is RT-safe.
142 rbuf->get_write_vector (&vec);
144 if (vec.len[0] == 0) {
145 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1: no space in per thread pool for request of type %2\n", name(), rt));
149 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1: allocated per-thread request of type %2, caller %3\n", name(), rt, pthread_self()));
151 vec.buf[0]->type = rt;
152 vec.buf[0]->valid = true;
156 /* calling thread has not registered, so just allocate a new request on
157 * the heap. the lack of registration implies that realtime constraints
161 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1: allocated normal heap request of type %2, caller %3\n", name(), rt, pthread_self()));
163 RequestObject* req = new RequestObject;
169 template <typename RequestObject> void
170 AbstractUI<RequestObject>::handle_ui_requests ()
172 RequestBufferMapIterator i;
173 RequestBufferVector vec;
175 /* check all registered per-thread buffers first */
177 request_buffer_map_lock.lock ();
179 for (i = request_buffers.begin(); i != request_buffers.end(); ++i) {
183 /* we must process requests 1 by 1 because
184 the request may run a recursive main
185 event loop that will itself call
186 handle_ui_requests. when we return
187 from the request handler, we cannot
188 expect that the state of queued requests
189 is even remotely consistent with
190 the condition before we called it.
193 i->second->get_read_vector (&vec);
195 if (vec.len[0] == 0) {
198 if (vec.buf[0]->valid) {
199 request_buffer_map_lock.unlock ();
200 do_request (vec.buf[0]);
201 request_buffer_map_lock.lock ();
202 if (vec.buf[0]->invalidation) {
203 vec.buf[0]->invalidation->requests.remove (vec.buf[0]);
205 i->second->increment_read_ptr (1);
211 /* clean up any dead request buffers (their thread has exited) */
213 for (i = request_buffers.begin(); i != request_buffers.end(); ) {
214 if ((*i).second->dead) {
215 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 deleting dead per-thread request buffer for %3 @ %4\n",
216 name(), pthread_self(), i->first, i->second));
218 RequestBufferMapIterator tmp = i;
220 request_buffers.erase (i);
227 request_buffer_map_lock.unlock ();
229 /* and now, the generic request buffer. same rules as above apply */
231 Glib::Threads::Mutex::Lock lm (request_list_lock);
233 while (!request_list.empty()) {
234 RequestObject* req = request_list.front ();
235 request_list.pop_front ();
237 /* We need to use this lock, because its the one
238 returned by slot_invalidation_mutex() and protects
239 against request invalidation.
242 request_buffer_map_lock.lock ();
244 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 handling invalid heap request, type %3, deleting\n", name(), pthread_self(), req->type));
246 request_buffer_map_lock.unlock ();
250 /* we're about to execute this request, so its
251 too late for any invalidation. mark
252 the request as "done" before we start.
255 if (req->invalidation) {
256 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 remove request from its invalidation list\n", name(), pthread_self()));
258 /* after this call, if the object referenced by the
259 * invalidation record is deleted, it will no longer
260 * try to mark the request as invalid.
263 req->invalidation->requests.remove (req);
266 /* at this point, an object involved in a functor could be
267 * deleted before we actually execute the functor. so there is
268 * a race condition that makes the invalidation architecture
269 * somewhat pointless.
271 * really, we should only allow functors containing shared_ptr
272 * references to objects to enter into the request queue.
275 request_buffer_map_lock.unlock ();
277 /* unlock the request lock while we execute the request, so
278 * that we don't needlessly block other threads (note: not RT
279 * threads since they have their own queue) from making requests.
284 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 execute request type %3\n", name(), pthread_self(), req->type));
286 /* and lets do it ... this is a virtual call so that each
287 * specific type of UI can have its own set of requests without
288 * some kind of central request type registration logic
293 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 delete heap request type %3\n", name(), pthread_self(), req->type));
296 /* re-acquire the list lock so that we check again */
302 template <typename RequestObject> void
303 AbstractUI<RequestObject>::send_request (RequestObject *req)
305 /* This is called to ask a given UI to carry out a request. It may be
306 * called from the same thread that runs the UI's event loop (see the
307 * caller_is_self() case below), or from any other thread.
310 if (base_instance() == 0) {
311 return; /* XXX is this the right thing to do ? */
314 if (caller_is_self ()) {
315 /* the thread that runs this UI's event loop is sending itself
316 a request: we dispatch it immediately and inline.
318 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 direct dispatch of request type %3\n", name(), pthread_self(), req->type));
322 /* If called from a different thread, we first check to see if
323 * the calling thread is registered with this UI. If so, there
324 * is a per-thread ringbuffer of requests that ::get_request()
325 * just set up a new request in. If so, all we need do here is
326 * to advance the write ptr in that ringbuffer so that the next
327 * request by this calling thread will use the next slot in
328 * the ringbuffer. The ringbuffer has
329 * single-reader/single-writer semantics because the calling
330 * thread is the only writer, and the UI event loop is the only
334 RequestBuffer* rbuf = per_thread_request_buffer.get ();
337 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 send per-thread request type %3\n", name(), pthread_self(), req->type));
338 rbuf->increment_write_ptr (1);
340 /* no per-thread buffer, so just use a list with a lock so that it remains
341 single-reader/single-writer semantics
343 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 send heap request type %3\n", name(), pthread_self(), req->type));
344 Glib::Threads::Mutex::Lock lm (request_list_lock);
345 request_list.push_back (req);
348 /* send the UI event loop thread a wakeup so that it will look
349 at the per-thread and generic request lists.
352 request_channel.wakeup ();
356 template<typename RequestObject> void
357 AbstractUI<RequestObject>::call_slot (InvalidationRecord* invalidation, const boost::function<void()>& f)
359 if (caller_is_self()) {
360 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 direct dispatch of call slot via functor @ %3, invalidation %4\n", name(), pthread_self(), &f, invalidation));
365 RequestObject *req = get_request (BaseUI::CallSlot);
371 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 queue call-slot using functor @ %3, invalidation %4\n", name(), pthread_self(), &f, invalidation));
373 /* copy semantics: copy the functor into the request object */
377 /* the invalidation record is an object which will carry out
378 * invalidation of any requests associated with it when it is
379 * destroyed. it can be null. if its not null, associate this
380 * request with the invalidation record. this allows us to
381 * "cancel" requests submitted to the UI because they involved
382 * a functor that uses an object that is being deleted.
385 req->invalidation = invalidation;
388 invalidation->requests.push_back (req);
389 invalidation->event_loop = this;