libpbd: changes to pre-registration of signal emitting threads

There is no need to preallocate request buffers for these threads - the event
loops that require them can allocate them when they discover and register the
pre-registered threads. This also means that event loops do not need to
register request buffer factories.
This commit is contained in:
Paul Davis 2023-04-21 13:43:46 -06:00
parent ba66381ab0
commit b0586763ba
7 changed files with 55 additions and 145 deletions

View file

@ -37,7 +37,7 @@ static void do_not_delete_the_loop_pointer (void*) { }
Glib::Threads::Private<EventLoop> EventLoop::thread_event_loop (do_not_delete_the_loop_pointer);
Glib::Threads::RWLock EventLoop::thread_buffer_requests_lock;
Glib::Threads::Mutex EventLoop::thread_buffer_requests_lock;
EventLoop::ThreadRequestBufferList EventLoop::thread_buffer_requests;
EventLoop::RequestBufferSuppliers EventLoop::request_buffer_suppliers;
@ -113,16 +113,13 @@ vector<EventLoop::ThreadBufferMapping>
EventLoop::get_request_buffers_for_target_thread (const std::string& target_thread)
{
vector<ThreadBufferMapping> ret;
Glib::Threads::RWLock::WriterLock lm (thread_buffer_requests_lock);
Glib::Threads::Mutex::Lock lm (thread_buffer_requests_lock);
DEBUG_TRACE (PBD::DEBUG::EventLoop, string_compose ("%1 look for request buffers via %2\n", pthread_name(), target_thread));
for (ThreadRequestBufferList::const_iterator x = thread_buffer_requests.begin(); x != thread_buffer_requests.end(); ++x) {
if (x->second.target_thread_name == target_thread) {
DEBUG_TRACE (PBD::DEBUG::EventLoop, string_compose ("for thread \"%1\", request buffer for %2/%3 thread %4\n", target_thread, x->first, x->second.emitting_thread));
ret.push_back (x->second);
}
for (auto const & tbr : thread_buffer_requests) {
DEBUG_TRACE (PBD::DEBUG::EventLoop, string_compose ("for thread \"%1\", request buffer for %2 (%3) thread %4\n", target_thread, tbr.emitting_thread, tbr.num_requests));
ret.push_back (tbr);
}
DEBUG_TRACE (PBD::DEBUG::EventLoop, string_compose ("for thread \"%1\", found %2 request buffers\n", target_thread, ret.size()));
@ -130,21 +127,6 @@ EventLoop::get_request_buffers_for_target_thread (const std::string& target_thre
return ret;
}
void
EventLoop::register_request_buffer_factory (const string& target_thread_name, void* (*factory)(uint32_t))
{
RequestBufferSupplier trs;
trs.name = target_thread_name;
trs.factory = factory;
{
Glib::Threads::RWLock::WriterLock lm (thread_buffer_requests_lock);
request_buffer_suppliers.push_back (trs);
}
DEBUG_TRACE (PBD::DEBUG::EventLoop, string_compose ("event loop %1 registered a buffer factory for %2\n", pthread_name(), target_thread_name));
}
void
EventLoop::pre_register (const string& emitting_thread_name, uint32_t num_requests)
{
@ -160,85 +142,56 @@ EventLoop::pre_register (const string& emitting_thread_name, uint32_t num_reques
*/
ThreadBufferMapping mapping;
Glib::Threads::RWLock::WriterLock lm (thread_buffer_requests_lock);
Glib::Threads::Mutex::Lock lm (thread_buffer_requests_lock);
for (RequestBufferSuppliers::iterator trs = request_buffer_suppliers.begin(); trs != request_buffer_suppliers.end(); ++trs) {
mapping.emitting_thread = pthread_self();
mapping.num_requests = num_requests;
if (!trs->factory) {
/* no factory - no request buffer required or expected */
continue;
}
/* now store it where the receiving thread (trs->name) can find
it if and when it is created. (Discovery happens in the
AbstractUI constructor. Note that if
*/
if (emitting_thread_name == trs->name) {
/* no need to register an emitter with itself */
continue;
}
/* management of the thread_request_buffers map works as
* follows:
*
* 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.
*/
mapping.emitting_thread = pthread_self();
mapping.target_thread_name = trs->name;
/* Allocate a suitably sized request buffer. This will set the
* thread-local variable that holds a pointer to this request
* buffer.
*/
mapping.request_buffer = trs->factory (num_requests);
/* now store it where the receiving thread (trs->name) can find
it if and when it is created. (Discovery happens in the
AbstractUI constructor. Note that if
*/
const string key = string_compose ("%1/%2", emitting_thread_name, mapping.target_thread_name);
/* management of the thread_request_buffers map works as
* follows:
*
* 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 (key was %4)\n",
emitting_thread_name, trs->name, mapping.request_buffer, key));
}
thread_buffer_requests.push_back (mapping);
DEBUG_TRACE (PBD::DEBUG::EventLoop, string_compose ("pre-registered thread \"%1\"\n", emitting_thread_name));
}
void
EventLoop::remove_request_buffer_from_map (void* ptr)
EventLoop::remove_request_buffer_from_map (pthread_t pth)
{
Glib::Threads::RWLock::WriterLock lm (thread_buffer_requests_lock);
Glib::Threads::Mutex::Lock 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) {
if (x->emitting_thread == pth) {
thread_buffer_requests.erase (x);
break;
}