4 #include "pbd/stacktrace.h"
5 #include "pbd/abstract_ui.h"
6 #include "pbd/pthread_utils.h"
7 #include "pbd/failed_constructor.h"
15 Glib::StaticPrivate<typename AbstractUI<R>::RequestBuffer> AbstractUI<R>::per_thread_request_buffer;
17 template<typename RequestBuffer> void
18 cleanup_request_buffer (void* ptr)
20 RequestBuffer* rb = (RequestBuffer*) ptr;
22 /* there is the question of why we don't simply erase the request
23 * buffer and delete it right here, since we have to take the lock
26 * as of april 24th 2012, i don't have a good answer to that.
31 Glib::Mutex::Lock lm (rb->ui.request_buffer_map_lock);
36 template <typename RequestObject>
37 AbstractUI<RequestObject>::AbstractUI (const string& name)
40 void (AbstractUI<RequestObject>::*pmf)(string,pthread_t,string,uint32_t) = &AbstractUI<RequestObject>::register_thread;
42 /* better to make this connect a handler that runs in the UI event loop but the syntax seems hard, and
43 register_thread() is thread safe anyway.
46 PBD::ThreadCreatedWithRequestSize.connect_same_thread (new_thread_connection, boost::bind (pmf, this, _1, _2, _3, _4));
49 template <typename RequestObject> void
50 AbstractUI<RequestObject>::register_thread (string target_gui, pthread_t thread_id, string /*thread name*/, uint32_t num_requests)
52 /* the calling thread wants to register with the thread that runs this
53 * UI's event loop, so that it will have its own per-thread queue of
54 * requests. this means that when it makes a request to this UI it can
55 * do so in a realtime-safe manner (no locks).
58 if (target_gui != name()) {
59 /* this UI is not the UI that the calling thread is trying to
65 /* the per_thread_request_buffer is a thread-private variable.
66 See pthreads documentation for more on these, but the key
67 thing is that it is a variable that as unique value for
68 each thread, guaranteed.
71 RequestBuffer* b = per_thread_request_buffer.get();
74 /* thread already registered with this UI
79 /* create a new request queue/ringbuffer */
81 b = new RequestBuffer (num_requests, *this);
84 /* add the new request queue (ringbuffer) to our map
85 so that we can iterate over it when the time is right.
86 This step is not RT-safe, but is assumed to be called
87 only at thread initialization time, not repeatedly,
88 and so this is of little consequence.
90 Glib::Mutex::Lock lm (request_buffer_map_lock);
91 request_buffers[thread_id] = b;
94 /* set this thread's per_thread_request_buffer to this new
95 queue/ringbuffer. remember that only this thread will
96 get this queue when it calls per_thread_request_buffer.get()
98 the second argument is a function that will be called
99 when the thread exits, and ensures that the buffer is marked
100 dead. it will then be deleted during a call to handle_ui_requests()
103 per_thread_request_buffer.set (b, cleanup_request_buffer<RequestBuffer>);
106 template <typename RequestObject> RequestObject*
107 AbstractUI<RequestObject>::get_request (RequestType rt)
109 RequestBuffer* rbuf = per_thread_request_buffer.get ();
110 RequestBufferVector vec;
112 /* see comments in ::register_thread() above for an explanation of
113 the per_thread_request_buffer variable
118 /* the calling thread has registered with this UI and therefore
119 * we have a per-thread request queue/ringbuffer. use it. this
120 * "allocation" of a request is RT-safe.
123 rbuf->get_write_vector (&vec);
125 if (vec.len[0] == 0) {
126 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1: no space in per thread pool for request of type %2\n", name(), rt));
130 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1: allocated per-thread request of type %2, caller %3\n", name(), rt, pthread_self()));
132 vec.buf[0]->type = rt;
133 vec.buf[0]->valid = true;
137 /* calling thread has not registered, so just allocate a new request on
138 * the heap. the lack of registration implies that realtime constraints
142 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1: allocated normal heap request of type %2, caller %3\n", name(), rt, pthread_self()));
144 RequestObject* req = new RequestObject;
150 template <typename RequestObject> void
151 AbstractUI<RequestObject>::handle_ui_requests ()
153 RequestBufferMapIterator i;
154 RequestBufferVector vec;
156 /* check all registered per-thread buffers first */
158 request_buffer_map_lock.lock ();
160 for (i = request_buffers.begin(); i != request_buffers.end(); ++i) {
164 /* we must process requests 1 by 1 because
165 the request may run a recursive main
166 event loop that will itself call
167 handle_ui_requests. when we return
168 from the request handler, we cannot
169 expect that the state of queued requests
170 is even remotely consistent with
171 the condition before we called it.
174 i->second->get_read_vector (&vec);
176 if (vec.len[0] == 0) {
179 if (vec.buf[0]->valid) {
180 request_buffer_map_lock.unlock ();
181 do_request (vec.buf[0]);
182 request_buffer_map_lock.lock ();
183 if (vec.buf[0]->invalidation) {
184 vec.buf[0]->invalidation->requests.remove (vec.buf[0]);
186 i->second->increment_read_ptr (1);
192 /* clean up any dead request buffers (their thread has exited) */
194 for (i = request_buffers.begin(); i != request_buffers.end(); ) {
195 if ((*i).second->dead) {
196 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 deleting dead per-thread request buffer for %3 @ %4\n",
197 name(), pthread_self(), i->first, i->second));
199 RequestBufferMapIterator tmp = i;
201 request_buffers.erase (i);
208 request_buffer_map_lock.unlock ();
210 /* and now, the generic request buffer. same rules as above apply */
212 Glib::Mutex::Lock lm (request_list_lock);
214 while (!request_list.empty()) {
215 RequestObject* req = request_list.front ();
216 request_list.pop_front ();
218 /* We need to use this lock, because its the one
219 returned by slot_invalidation_mutex() and protects
220 against request invalidation.
223 request_buffer_map_lock.lock ();
225 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 handling invalid heap request, type %3, deleting\n", name(), pthread_self(), req->type));
227 request_buffer_map_lock.unlock ();
231 /* we're about to execute this request, so its
232 too late for any invalidation. mark
233 the request as "done" before we start.
236 if (req->invalidation) {
237 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 remove request from its invalidation list\n", name(), pthread_self()));
239 /* after this call, if the object referenced by the
240 * invalidation record is deleted, it will no longer
241 * try to mark the request as invalid.
244 req->invalidation->requests.remove (req);
247 /* at this point, an object involved in a functor could be
248 * deleted before we actually execute the functor. so there is
249 * a race condition that makes the invalidation architecture
250 * somewhat pointless.
252 * really, we should only allow functors containing shared_ptr
253 * references to objects to enter into the request queue.
256 request_buffer_map_lock.unlock ();
258 /* unlock the request lock while we execute the request, so
259 * that we don't needlessly block other threads (note: not RT
260 * threads since they have their own queue) from making requests.
265 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 execute request type %3\n", name(), pthread_self(), req->type));
267 /* and lets do it ... this is a virtual call so that each
268 * specific type of UI can have its own set of requests without
269 * some kind of central request type registration logic
274 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 delete heap request type %3\n", name(), pthread_self(), req->type));
277 /* re-acquire the list lock so that we check again */
283 template <typename RequestObject> void
284 AbstractUI<RequestObject>::send_request (RequestObject *req)
286 /* This is called to ask a given UI to carry out a request. It may be
287 * called from the same thread that runs the UI's event loop (see the
288 * caller_is_self() case below), or from any other thread.
291 if (base_instance() == 0) {
292 return; /* XXX is this the right thing to do ? */
295 if (caller_is_self ()) {
296 /* the thread that runs this UI's event loop is sending itself
297 a request: we dispatch it immediately and inline.
299 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 direct dispatch of request type %3\n", name(), pthread_self(), req->type));
303 /* If called from a different thread, we first check to see if
304 * the calling thread is registered with this UI. If so, there
305 * is a per-thread ringbuffer of requests that ::get_request()
306 * just set up a new request in. If so, all we need do here is
307 * to advance the write ptr in that ringbuffer so that the next
308 * request by this calling thread will use the next slot in
309 * the ringbuffer. The ringbuffer has
310 * single-reader/single-writer semantics because the calling
311 * thread is the only writer, and the UI event loop is the only
315 RequestBuffer* rbuf = per_thread_request_buffer.get ();
318 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 send per-thread request type %3\n", name(), pthread_self(), req->type));
319 rbuf->increment_write_ptr (1);
321 /* no per-thread buffer, so just use a list with a lock so that it remains
322 single-reader/single-writer semantics
324 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 send heap request type %3\n", name(), pthread_self(), req->type));
325 Glib::Mutex::Lock lm (request_list_lock);
326 request_list.push_back (req);
329 /* send the UI event loop thread a wakeup so that it will look
330 at the per-thread and generic request lists.
333 request_channel.wakeup ();
337 template<typename RequestObject> void
338 AbstractUI<RequestObject>::call_slot (InvalidationRecord* invalidation, const boost::function<void()>& f)
340 if (caller_is_self()) {
341 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));
346 RequestObject *req = get_request (BaseUI::CallSlot);
352 DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1/%2 queue call-slot using functor @ %3, invalidation %4\n", name(), pthread_self(), &f, invalidation));
354 /* copy semantics: copy the functor into the request object */
358 /* the invalidation record is an object which will carry out
359 * invalidation of any requests associated with it when it is
360 * destroyed. it can be null. if its not null, associate this
361 * request with the invalidation record. this allows us to
362 * "cancel" requests submitted to the UI because they involved
363 * a functor that uses an object that is being deleted.
366 req->invalidation = invalidation;
369 invalidation->requests.push_back (req);
370 invalidation->event_loop = this;