mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-10 04:30:12 +02:00
Listen on the group socket and handle requests
This commit is contained in:
@@ -108,3 +108,7 @@ AEffect& update_aeffect(AEffect& plugin, const AEffect& updated_plugin) {
|
|||||||
|
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PluginParameters::operator==(const PluginParameters& rhs) const {
|
||||||
|
return plugin_path == rhs.plugin_path && socket_path == rhs.socket_path;
|
||||||
|
}
|
||||||
|
|||||||
@@ -571,3 +571,30 @@ struct AudioBuffers {
|
|||||||
s.value4b(sample_frames);
|
s.value4b(sample_frames);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object containing the startup options for hosting a plugin in a plugin
|
||||||
|
* group process. These are the exact same options that would have been passed
|
||||||
|
* to `yabridge-host.exe` were the plugin to be hosted individually.
|
||||||
|
*/
|
||||||
|
struct PluginParameters {
|
||||||
|
std::string plugin_path;
|
||||||
|
std::string socket_path;
|
||||||
|
|
||||||
|
bool operator==(const PluginParameters& rhs) const;
|
||||||
|
|
||||||
|
template <typename S>
|
||||||
|
void serialize(S& s) {
|
||||||
|
s.text1b(plugin_path, 4096);
|
||||||
|
s.text1b(socket_path, 4096);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct std::hash<PluginParameters> {
|
||||||
|
std::size_t operator()(PluginParameters const& params) const noexcept {
|
||||||
|
std::hash<string> hasher{};
|
||||||
|
|
||||||
|
return hasher(params.plugin_path) ^ (hasher(params.socket_path) << 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -121,7 +121,12 @@ PluginBridge::PluginBridge(audioMasterCallback host_callback)
|
|||||||
std::thread([&]() {
|
std::thread([&]() {
|
||||||
using namespace std::literals::chrono_literals;
|
using namespace std::literals::chrono_literals;
|
||||||
|
|
||||||
// TODO: Figure out how this check would work with plugin gruops
|
// TODO: For plugin groups, we should be polling whether someone is
|
||||||
|
// still listening on the group socket (and ideally we should be
|
||||||
|
// able to tell it's the same process). If we can't figure out a
|
||||||
|
// better way, then we could just return the PID when sending the
|
||||||
|
// host request to the group process and check whether that
|
||||||
|
// process is still running.
|
||||||
while (true) {
|
while (true) {
|
||||||
if (finished_accepting_sockets) {
|
if (finished_accepting_sockets) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
#include <boost/asio/read_until.hpp>
|
#include <boost/asio/read_until.hpp>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
||||||
|
#include "../../common/communication.h"
|
||||||
|
|
||||||
// FIXME: `std::filesystem` is broken in wineg++, at least under Wine 5.8. Any
|
// FIXME: `std::filesystem` is broken in wineg++, at least under Wine 5.8. Any
|
||||||
// path operation will thrown an encoding related error
|
// path operation will thrown an encoding related error
|
||||||
namespace fs = boost::filesystem;
|
namespace fs = boost::filesystem;
|
||||||
@@ -29,6 +31,13 @@ namespace fs = boost::filesystem;
|
|||||||
*/
|
*/
|
||||||
std::string create_logger_prefix(const fs::path& socket_path);
|
std::string create_logger_prefix(const fs::path& socket_path);
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
using handle_host_plugin_parameters = std::pair<GroupBridge*, PluginParameters>;
|
||||||
|
|
||||||
|
uint32_t WINAPI handle_host_plugin_proxy(void* param);
|
||||||
|
|
||||||
StdIoCapture::StdIoCapture(boost::asio::io_context& io_context,
|
StdIoCapture::StdIoCapture(boost::asio::io_context& io_context,
|
||||||
int file_descriptor)
|
int file_descriptor)
|
||||||
: pipe(io_context),
|
: pipe(io_context),
|
||||||
@@ -54,10 +63,6 @@ StdIoCapture::~StdIoCapture() {
|
|||||||
close(pipe_fd[0]);
|
close(pipe_fd[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PluginParameters::operator==(const PluginParameters& other) const {
|
|
||||||
return removeme == other.removeme;
|
|
||||||
}
|
|
||||||
|
|
||||||
GroupBridge::GroupBridge(boost::filesystem::path group_socket_path)
|
GroupBridge::GroupBridge(boost::filesystem::path group_socket_path)
|
||||||
: logger(Logger::create_from_environment(
|
: logger(Logger::create_from_environment(
|
||||||
create_logger_prefix(group_socket_path))),
|
create_logger_prefix(group_socket_path))),
|
||||||
@@ -77,18 +82,58 @@ GroupBridge::GroupBridge(boost::filesystem::path group_socket_path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GroupBridge::handle_host_plugin(const PluginParameters& parameters) {
|
void GroupBridge::handle_host_plugin(const PluginParameters& parameters) {
|
||||||
// TODO: Start the plugin,
|
// TODO: Start the plugin
|
||||||
// TODO: Allow this process to exit when the last plugin exits. Make sure
|
// TODO: Allow this process to exit when the last plugin exits. Make sure
|
||||||
// that that doesn't cause any race conditions.
|
// that that doesn't cause any race conditions.
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupBridge::handle_incoming_connections() {
|
void GroupBridge::handle_incoming_connections() {
|
||||||
// TODO: Accept connections here
|
accept_requests();
|
||||||
|
|
||||||
logger.log("Now accepting incoming connections");
|
logger.log("Now accepting incoming connections");
|
||||||
io_context.run();
|
io_context.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GroupBridge::accept_requests() {
|
||||||
|
group_socket_acceptor.async_accept(
|
||||||
|
[&](const boost::system::error_code& error,
|
||||||
|
boost::asio::local::stream_protocol::socket socket) {
|
||||||
|
// Stop the whole process when the socket gets closed unexpectedly
|
||||||
|
if (error.failed()) {
|
||||||
|
logger.log("Error while listening for incoming connections:");
|
||||||
|
logger.log(error.message());
|
||||||
|
|
||||||
|
io_context.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the parameters, and then host the plugin in this process
|
||||||
|
// just like if we would be hosting the plugin individually through
|
||||||
|
// `yabridge-hsot.exe`. Since we're using sockets there's no reason
|
||||||
|
// to send an acknowledgement back. One potential issue here is that
|
||||||
|
// it will be hard to tell whether a plugin has crashed before
|
||||||
|
// initialization, and that the sockets will never be accepted. For
|
||||||
|
// individually hosted plugins we poll whether the Wine process is
|
||||||
|
// still active so we can terminate early if it is not, but in this
|
||||||
|
// case the yabridge instance has to determine that this process is
|
||||||
|
// still running.
|
||||||
|
const auto parameters = read_object<PluginParameters>(socket);
|
||||||
|
|
||||||
|
// Collisions in the generated socket names should be very rare, but
|
||||||
|
// it could in theory happen
|
||||||
|
std::lock_guard lock(active_plugins_mutex);
|
||||||
|
assert(active_plugins.find(parameters) != active_plugins.end());
|
||||||
|
|
||||||
|
// CreateThread() doesn't support multiple arguments and requires
|
||||||
|
// manualy memory management.
|
||||||
|
handle_host_plugin_parameters* thread_params =
|
||||||
|
new std::pair<GroupBridge*, PluginParameters>(this, parameters);
|
||||||
|
active_plugins[parameters] =
|
||||||
|
Win32Thread(handle_host_plugin_proxy, &thread_params);
|
||||||
|
|
||||||
|
accept_requests();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void GroupBridge::async_log_pipe_lines(
|
void GroupBridge::async_log_pipe_lines(
|
||||||
boost::asio::posix::stream_descriptor& pipe,
|
boost::asio::posix::stream_descriptor& pipe,
|
||||||
boost::asio::streambuf& buffer,
|
boost::asio::streambuf& buffer,
|
||||||
@@ -135,3 +180,16 @@ std::string create_logger_prefix(const fs::path& socket_path) {
|
|||||||
|
|
||||||
return "[" + socket_name + "] ";
|
return "[" + socket_name + "] ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t WINAPI handle_host_plugin_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);
|
||||||
|
GroupBridge* instance = thread_params->first;
|
||||||
|
PluginParameters& parameters = thread_params->second;
|
||||||
|
delete thread_params;
|
||||||
|
|
||||||
|
instance->handle_host_plugin(parameters);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,21 +24,6 @@
|
|||||||
|
|
||||||
#include "vst2.h"
|
#include "vst2.h"
|
||||||
|
|
||||||
// TODO: Replace this with a proper struct that contains the required arguments
|
|
||||||
// for creating a PluginBridge instance
|
|
||||||
struct PluginParameters {
|
|
||||||
int removeme;
|
|
||||||
|
|
||||||
bool operator==(const PluginParameters& p) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct std::hash<PluginParameters> {
|
|
||||||
std::size_t operator()(PluginParameters const& params) const noexcept {
|
|
||||||
return std::hash<int>{}(params.removeme);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulate capturing the STDOUT or STDERR stream by opening a pipe and
|
* Encapsulate capturing the STDOUT or STDERR stream by opening a pipe and
|
||||||
* reopening the passed file descriptor as one of the ends of the newly opened
|
* reopening the passed file descriptor as one of the ends of the newly opened
|
||||||
@@ -123,6 +108,8 @@ class GroupBridge {
|
|||||||
* where `<wine_prefix_id>` is a numerical hash as explained in the
|
* where `<wine_prefix_id>` is a numerical hash as explained in the
|
||||||
* `create_logger_prefix()` function in `./group.cpp`.
|
* `create_logger_prefix()` function in `./group.cpp`.
|
||||||
*
|
*
|
||||||
|
* @throw boost::system::system_error If we can't listen on the socket.
|
||||||
|
*
|
||||||
* @note Creating an `GroupBridge` instance has the side effect that the
|
* @note Creating an `GroupBridge` instance has the side effect that the
|
||||||
* STDOUT and STDERR streams of the current process will be redirected to
|
* STDOUT and STDERR streams of the current process will be redirected to
|
||||||
* a pipe so they can be properly written to a log file.
|
* a pipe so they can be properly written to a log file.
|
||||||
@@ -132,10 +119,10 @@ class GroupBridge {
|
|||||||
/**
|
/**
|
||||||
* Host a new plugin within this process. Called by proxy using
|
* Host a new plugin within this process. Called by proxy using
|
||||||
* `handle_host_plugin_proxy()` in `./group.cpp` because the Win32
|
* `handle_host_plugin_proxy()` in `./group.cpp` because the Win32
|
||||||
* `CreateThread` API only allows passing a single pointer to the function.
|
* `CreateThread` API only allows passing a single pointer to the function
|
||||||
* Because we don't have access to our own thread handle, the thread that's
|
* and does not allow lambdas. Because we don't have access to our own
|
||||||
* listening on the group socket and that has created this thread will also
|
* thread handle, the thread that's listening on the group socket will have
|
||||||
* add this thread to the `active_plugins` map.
|
* already added this thread to the `active_plugins` map.
|
||||||
*
|
*
|
||||||
* Once the plugin has exited, this thread will then remove itself from the
|
* Once the plugin has exited, this thread will then remove itself from the
|
||||||
* `active_plugins` map. If this causes the vector to become empty, we will
|
* `active_plugins` map. If this causes the vector to become empty, we will
|
||||||
@@ -158,6 +145,17 @@ class GroupBridge {
|
|||||||
void handle_incoming_connections();
|
void handle_incoming_connections();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* Listen on the group socket for incoming requests to host a new plugin
|
||||||
|
* within this group process. This will asynchronously listen on the socket,
|
||||||
|
* and for any connection made it will retrieve a `PluginParameters` object
|
||||||
|
* containing information about the plugin to host and then spawn a new
|
||||||
|
* thread to start hosting that plugin.
|
||||||
|
*
|
||||||
|
* @see handle_host_plugin
|
||||||
|
*/
|
||||||
|
void accept_requests();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Continuously read from a pipe and write the output to the log file. Used
|
* Continuously read from a pipe and write the output to the log file. Used
|
||||||
* with the IO streams captured by `stdout_redirect` and `stderr_redirect`.
|
* with the IO streams captured by `stdout_redirect` and `stderr_redirect`.
|
||||||
@@ -206,11 +204,11 @@ class GroupBridge {
|
|||||||
/**
|
/**
|
||||||
* A map of threads that are currently hosting a plugin within this process.
|
* A map of threads that are currently hosting a plugin within this process.
|
||||||
* After a plugin has exited or its initialization has failed, the thread
|
* After a plugin has exited or its initialization has failed, the thread
|
||||||
* handling it will remove itself from this map.
|
* handling it will remove itself from this map. This is to keep track of
|
||||||
|
* the amount of plugins currently running with their associated thread
|
||||||
|
* handles.
|
||||||
*/
|
*/
|
||||||
std::unordered_map<PluginParameters,
|
std::unordered_map<PluginParameters, Win32Thread> active_plugins;
|
||||||
std::pair<Win32Thread, std::unique_ptr<Vst2Bridge>>>
|
|
||||||
active_plugins;
|
|
||||||
/**
|
/**
|
||||||
* A mutex to prevent two threads from simultaneously accessing the plugins
|
* A mutex to prevent two threads from simultaneously accessing the plugins
|
||||||
* map, and also to prevent `handle_host_plugin()` from terminating the
|
* map, and also to prevent `handle_host_plugin()` from terminating the
|
||||||
|
|||||||
@@ -56,31 +56,29 @@ int __cdecl main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
const std::string group_socket_endpoint_path(argv[1]);
|
const std::string group_socket_endpoint_path(argv[1]);
|
||||||
|
|
||||||
// TODO: Catch exception during initialization when another process is
|
std::cerr << "Initializing yabridge group host version "
|
||||||
// already listening on the socket. Make sure to print a more useful
|
<< yabridge_git_version
|
||||||
// message instead.
|
|
||||||
try {
|
|
||||||
std::cerr << "Initializing yabridge group host version "
|
|
||||||
<< yabridge_git_version
|
|
||||||
#ifdef __i386__
|
#ifdef __i386__
|
||||||
<< " (32-bit compatibility mode)"
|
<< " (32-bit compatibility mode)"
|
||||||
#endif
|
#endif
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
|
||||||
|
try {
|
||||||
GroupBridge bridge(group_socket_endpoint_path);
|
GroupBridge bridge(group_socket_endpoint_path);
|
||||||
|
|
||||||
// Blocks the main thread until all plugins have exited
|
// Blocks the main thread until all plugins have exited
|
||||||
bridge.handle_incoming_connections();
|
bridge.handle_incoming_connections();
|
||||||
} catch (const std::runtime_error& error) {
|
} catch (const boost::system::system_error& error) {
|
||||||
// Even though the process will likely outlive the yabridge instance
|
// If another process is already listening on the socket, we'll just
|
||||||
// that spawns it, we can still print any initialization messages and
|
// print a message and exit quietly. This could happen if the host
|
||||||
// errors to STDERR since at this point there will still be a yabridge
|
// starts multiple yabridge instances that all use the same plugin group
|
||||||
// instance capturing this process's output.
|
// at the same time.
|
||||||
// TODO: Check if this is printed on the right stream
|
// TODO: Check if this is printed on the right stream
|
||||||
std::cerr << "Error while initializing the group host process:"
|
std::cerr << "Another process is already listening on this group's "
|
||||||
|
"socket, connecting to the existing process:"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
std::cerr << error.what() << std::endl;
|
std::cerr << error.what() << std::endl;
|
||||||
|
|
||||||
return 1;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ int __cdecl main(int argc, char* argv[]) {
|
|||||||
<< " (32-bit compatibility mode)"
|
<< " (32-bit compatibility mode)"
|
||||||
#endif
|
#endif
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Vst2Bridge bridge(plugin_dll_path, socket_endpoint_path);
|
Vst2Bridge bridge(plugin_dll_path, socket_endpoint_path);
|
||||||
std::cerr << "Finished initializing '" << plugin_dll_path << "'"
|
std::cerr << "Finished initializing '" << plugin_dll_path << "'"
|
||||||
|
|||||||
Reference in New Issue
Block a user