Init plugins and handle events on the main thread

Within the plugin IO context.
This commit is contained in:
Robbert van der Helm
2020-05-25 15:19:46 +02:00
parent bbfe522343
commit 064bb2684f
2 changed files with 112 additions and 64 deletions
+61 -43
View File
@@ -52,16 +52,16 @@ boost::asio::local::stream_protocol::acceptor create_acceptor_if_inactive(
*/
std::string create_logger_prefix(const fs::path& socket_path);
uint32_t WINAPI handle_host_plugin_proxy(void* param);
uint32_t WINAPI handle_plugin_dispatch_proxy(void* param);
/**
* CreateThread() is great and allows you to pass a single value to the
* function, so we'll use this to pass both `this` and the parameters to the
* below thread function so it can do its thing.
*
* @relates handle_host_plugin_proxy
* @relates handle_plugin_dispatch_proxy
*/
using handle_host_plugin_parameters = std::pair<GroupBridge*, GroupRequest>;
using handle_plugin_dispatch_parameters = std::pair<GroupBridge*, GroupRequest>;
StdIoCapture::StdIoCapture(boost::asio::io_context& io_context,
int file_descriptor)
@@ -91,15 +91,17 @@ StdIoCapture::~StdIoCapture() {
GroupBridge::GroupBridge(boost::filesystem::path group_socket_path)
: logger(Logger::create_from_environment(
create_logger_prefix(group_socket_path))),
io_context(),
plugin_context(),
stdio_context(),
stdout_redirect(stdio_context, STDOUT_FILENO),
stderr_redirect(stdio_context, STDERR_FILENO),
group_socket_endpoint(group_socket_path.string()),
group_socket_acceptor(
create_acceptor_if_inactive(io_context, group_socket_endpoint)),
shutdown_timer(io_context) {
create_acceptor_if_inactive(plugin_context, group_socket_endpoint)),
shutdown_timer(plugin_context) {
// Write this process's original STDOUT and STDERR streams to the logger
// TODO: This works for output generated by plugins, but not for debug
// messages generated by wineserver. Is it possible to catch those?
async_log_pipe_lines(stdout_redirect.pipe, stdout_buffer, "[STDOUT] ");
async_log_pipe_lines(stderr_redirect.pipe, stderr_buffer, "[STDERR] ");
@@ -111,36 +113,30 @@ GroupBridge::~GroupBridge() {
stdio_handler.join();
}
void GroupBridge::handle_host_plugin(const GroupRequest request) {
void GroupBridge::handle_plugin_dispatch(const GroupRequest request) {
using namespace std::literals::chrono_literals;
// At this point the `active_plugins` map will already contain a copy of
// `parameters` along with this thread's handle
// The initialization process for a plugin is identical to that in
// `../individual-host.cpp`
logger.log("Received request to host '" + request.plugin_path +
"' using socket '" + request.socket_path + "'");
try {
Vst2Bridge bridge(request.plugin_path, request.socket_path);
logger.log("Finished initializing '" + request.plugin_path + "'");
// At this point the `active_plugins` map will already contain the
// intialized plugin's `Vst2Bridge` instance and this thread's handle
auto& [thread, bridge] = active_plugins.at(request);
// Blocks the main thread until the plugin shuts down
bridge.handle_dispatch();
// Blocks the main thread until the plugin shuts down, handling all events
// on the main IO context
// TODO: Maybe add a function to each vstbridge that returns whether the
// message loop should be postponed, and pass a function here that
// checks if this is the case for any of the plugins?
bridge->handle_dispatch_multi(
plugin_context, [&]() { return should_postpone_message_loop(); });
logger.log("'" + request.plugin_path + "' has exited");
logger.log("'" + request.plugin_path + "' has exited");
} catch (const std::runtime_error& error) {
logger.log("Error while initializing '" + request.plugin_path + "':");
logger.log(error.what());
}
// After the plugin has exited (either after `effClose()` or because it
// failed to initialize), we'll remove this thread's plugin from the active
// plugins. If no active plugins remain, then we'll terminate this process.
// After the plugin has exited, we'll remove this thread's plugin from the
// active plugins. If no active plugins remain, then we'll terminate the
// process.
std::lock_guard lock(active_plugins_mutex);
active_plugins.erase(request);
// Defer shutting down the process to allow for fast plugin scanning by
// allowing plugins to reuse the same group host process
// Defer actually shutting down the process to allow for fast plugin
// scanning by allowing plugins to reuse the same group host process
shutdown_timer.expires_after(2s);
shutdown_timer.async_wait([&](const boost::system::error_code& error) {
// A previous timer gets canceled automatically when another plugin
@@ -153,7 +149,7 @@ void GroupBridge::handle_host_plugin(const GroupRequest request) {
if (active_plugins.size() == 0) {
logger.log(
"All plugins have exited, shutting down the group process");
io_context.stop();
plugin_context.stop();
}
});
}
@@ -163,7 +159,12 @@ void GroupBridge::handle_incoming_connections() {
logger.log(
"Group host is up and running, now accepting incoming connections");
io_context.run();
plugin_context.run();
}
bool GroupBridge::should_postpone_message_loop() {
// TODO: Iterate over the plugins
return false;
}
void GroupBridge::accept_requests() {
@@ -177,7 +178,7 @@ void GroupBridge::accept_requests() {
logger.log("Error while listening for incoming connections:");
logger.log(error.message());
io_context.stop();
plugin_context.stop();
}
// Read the parameters, and then host the plugin in this process
@@ -186,19 +187,36 @@ void GroupBridge::accept_requests() {
// yabridge plugin will be able to tell if the plugin has caused
// this process to crash during its initialization to prevent
// waiting indefinitely on the sockets to be connected to.
const auto parameters = read_object<GroupRequest>(socket);
const auto request = read_object<GroupRequest>(socket);
write_object(socket, GroupResponse{boost::this_process::get_id()});
// Collisions in the generated socket names should be very rare, but
// it could in theory happen
assert(active_plugins.find(parameters) == active_plugins.end());
assert(active_plugins.find(request) == active_plugins.end());
// CreateThread() doesn't support multiple arguments and requires
// manualy memory management.
handle_host_plugin_parameters* thread_params =
new std::pair<GroupBridge*, GroupRequest>(this, parameters);
active_plugins[parameters] =
Win32Thread(handle_host_plugin_proxy, thread_params);
// The plugin has to be initiated on the IO context's thread because
// this has to be done on the same thread that's handling messages,
// and all window messages have to be handled from the same thread.
logger.log("Received request to host '" + request.plugin_path +
"' using socket '" + request.socket_path + "'");
try {
auto bridge = std::make_unique<Vst2Bridge>(request.plugin_path,
request.socket_path);
logger.log("Finished initializing '" + request.plugin_path +
"'");
// CreateThread() doesn't support multiple arguments and
// requires manualy memory management.
handle_plugin_dispatch_parameters* thread_params =
new std::pair<GroupBridge*, GroupRequest>(this, request);
active_plugins[request] = std::pair(
Win32Thread(handle_plugin_dispatch_proxy, thread_params),
std::move(bridge));
} catch (const std::runtime_error& error) {
logger.log("Error while initializing '" + request.plugin_path +
"':");
logger.log(error.what());
}
accept_requests();
});
@@ -284,15 +302,15 @@ std::string create_logger_prefix(const fs::path& socket_path) {
return "[" + socket_name + "] ";
}
uint32_t WINAPI handle_host_plugin_proxy(void* param) {
uint32_t WINAPI handle_plugin_dispatch_proxy(void* param) {
// The Win32 API only allows you to pass a void pointer to threads, so we
// need to use manual memory management.
auto thread_params = static_cast<handle_host_plugin_parameters*>(param);
auto thread_params = static_cast<handle_plugin_dispatch_parameters*>(param);
GroupBridge* instance = thread_params->first;
GroupRequest parameters = thread_params->second;
delete thread_params;
instance->handle_host_plugin(parameters);
instance->handle_plugin_dispatch(parameters);
return 0;
}