PBD::Signal<...>::connect() is already thread safe, so drop intermediate proxy/call_s...
[ardour.git] / libs / pbd / event_loop.cc
index 11177610030e9a694d6711961715bfac1e10f1ca..e003a8d37a0ccaa635c6c8eab757dbdc939d326f 100644 (file)
 
 */
 
+#include <cstring>
+
+#include <pthread.h>
+
 #include "pbd/compose.h"
 #include "pbd/debug.h"
 #include "pbd/event_loop.h"
 #include "pbd/error.h"
 #include "pbd/stacktrace.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 
 using namespace PBD;
 using namespace std;
@@ -41,6 +45,18 @@ EventLoop::EventLoop (string const& name)
 {
 }
 
+EventLoop::~EventLoop ()
+{
+       trash.sort();
+       trash.unique();
+       for (std::list<InvalidationRecord*>::iterator r = trash.begin(); r != trash.end(); ++r) {
+               if (!(*r)->in_use ()) {
+                       delete *r;
+               }
+       }
+       trash.clear ();
+}
+
 EventLoop*
 EventLoop::get_event_loop_for_thread()
 {
@@ -56,7 +72,7 @@ EventLoop::set_event_loop_for_thread (EventLoop* loop)
 void*
 EventLoop::invalidate_request (void* data)
 {
-        InvalidationRecord* ir = (InvalidationRecord*) data;
+       InvalidationRecord* ir = (InvalidationRecord*) data;
 
        /* Some of the requests queued with an EventLoop may involve functors
         * that make method calls to objects whose lifetime is shorter
@@ -82,16 +98,14 @@ EventLoop::invalidate_request (void* data)
         * inherit (indirectly) from sigc::trackable.
         */
 
-        if (ir->event_loop) {
+       if (ir->event_loop) {
+               DEBUG_TRACE (PBD::DEBUG::AbstractUI, string_compose ("%1: EventLoop::invalidate_request %2\n", ir->event_loop, ir));
                Glib::Threads::Mutex::Lock lm (ir->event_loop->slot_invalidation_mutex());
-               for (list<BaseRequestObject*>::iterator i = ir->requests.begin(); i != ir->requests.end(); ++i) {
-                       (*i)->valid = false;
-                       (*i)->invalidation = 0;
-               }
-               delete ir;
-        }
+               ir->invalidate ();
+               ir->event_loop->trash.push_back(ir);
+       }
 
-        return 0;
+       return 0;
 }
 
 vector<EventLoop::ThreadBufferMapping>
@@ -143,7 +157,7 @@ EventLoop::pre_register (const string& emitting_thread_name, uint32_t num_reques
         */
 
        ThreadBufferMapping mapping;
-       Glib::Threads::RWLock::ReaderLock lm (thread_buffer_requests_lock);
+       Glib::Threads::RWLock::WriterLock lm (thread_buffer_requests_lock);
 
        for (RequestBufferSuppliers::iterator trs = request_buffer_suppliers.begin(); trs != request_buffer_suppliers.end(); ++trs) {
 
@@ -171,24 +185,59 @@ EventLoop::pre_register (const string& emitting_thread_name, uint32_t num_reques
                   AbstractUI constructor. Note that if
                */
 
-               /* make a key composed of the emitter and receiver thread names */
+               const string key = string_compose ("%1/%2", emitting_thread_name, mapping.target_thread_name);
 
-               string key = emitting_thread_name;
-               key += '/';
-               key +=  mapping.target_thread_name;
-
-               /* if the emitting thread was killed and recreated (with the
-                * same name), this will replace the entry in
-                * thread_buffer_requests. The old entry will be lazily deleted
-                * when the target thread finds the request buffer and realizes
-                * that it is dead.
+               /* management of the thread_request_buffers map works as
+                * follows:
                 *
-                * If the request buffer is replaced before the target thread
-                * ever finds the dead version, we will leak the old request
-                * buffer.
+                * when the factory method was called above, the pointer to the
+                * created buffer is set as a thread-local-storage (TLS) value
+                * for this (the emitting) thread.
+                *
+                * The TLS value is set up with a destructor that marks the
+                * request buffer as "dead" when the emitting thread exits.
+                *
+                * An entry will remain in the map after the thread exits.
+                *
+                * The receiving thread may (if it receives requests from other
+                * threads) notice the dead buffer. If it does, it will delete
+                * the request buffer, and call
+                * ::remove_request_buffer_from_map() to get rid of it from the map.
+                *
+                * This does mean that the lifetime of the request buffer is
+                * indeterminate: if the receiving thread were to receive no
+                * further requests, the request buffer will live on
+                * forever. But this is OK, because if there are no requests
+                * arriving, the receiving thread is not attempting to use the
+                * request buffer(s) in any way.
+                *
+                * Note, however, that *if* an emitting thread is recreated
+                * with the same name (e.g. when a control surface is
+                * enabled/disabled/enabled), then the request buffer for the
+                * new thread will replace the map entry for the key, because
+                * of the matching thread names. This does mean that
+                * potentially the request buffer can leak in this case, but
+                * (a) these buffers are not really that large anyway (b) the
+                * scenario is not particularly common (c) the buffers would
+                * typically last across a session instance if not program
+                * lifetime anyway.
                 */
 
                thread_buffer_requests[key] = mapping;
-               DEBUG_TRACE (PBD::DEBUG::EventLoop, string_compose ("pre-registered request buffer for \"%1\" to send to \"%2\", buffer @ %3\n", emitting_thread_name, trs->name, mapping.request_buffer));
+               DEBUG_TRACE (PBD::DEBUG::EventLoop, string_compose ("pre-registered request buffer for \"%1\" to send to \"%2\", buffer @ %3 (key was %4)\n",
+                                                                   emitting_thread_name, trs->name, mapping.request_buffer, key));
+       }
+}
+
+void
+EventLoop::remove_request_buffer_from_map (void* ptr)
+{
+       Glib::Threads::RWLock::WriterLock lm (thread_buffer_requests_lock);
+
+       for (ThreadRequestBufferList::iterator x = thread_buffer_requests.begin(); x != thread_buffer_requests.end(); ++x) {
+               if (x->second.request_buffer == ptr) {
+                       thread_buffer_requests.erase (x);
+                       break;
+               }
        }
 }