Replace Boost.Asio with standalone Asio library

We had to add an even hackier hack now to get Boost.Process to
interoperate with Asio's IO contexts. This will be replaced later when
we replace Boost.Process.
This commit is contained in:
Robbert van der Helm
2022-04-10 21:29:14 +02:00
parent d9ab04ba35
commit 556b0e38f9
34 changed files with 465 additions and 200 deletions
+52
View File
@@ -0,0 +1,52 @@
// yabridge: a Wine VST bridge
// Copyright (C) 2020-2022 Robbert van der Helm
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
// Libraries like (Boost.)Asio think we're compiling on Windows or using a MSVC
// toolchain. This will cause them to make incorrect assumptions which platform
// specific features are available. The only way around this I could think of
// was to just temporarily undefine the macros these libraries use to detect
// it's running under a WIN32 environment. If anyone knows a better way to do
// this, please let me know!
#pragma push_macro("WIN32")
#pragma push_macro("_WIN32")
#pragma push_macro("__WIN32__")
#pragma push_macro("_WIN64")
#undef WIN32
#undef _WIN32
#undef __WIN32__
#undef _WIN64
// This would be the minimal include needed to get Asio to work. The commented
// out includes are the actual header that would cause compile errors if not
// included here, but including headers from the detail directory directly
// didn't sound like a great idea.
// FIXME: Remove Boost stuff
#include <boost/predef.h>
#include <asio/basic_socket_streambuf.hpp>
#include <boost/asio/basic_socket_streambuf.hpp>
#include <boost/interprocess/mapped_region.hpp>
// #include <asio/asio/detail/timer_queue_ptime.hpp>
// #include <boost/interprocess/detail/workaround.hpp>
#pragma pop_macro("WIN32")
#pragma pop_macro("_WIN32")
#pragma pop_macro("__WIN32__")
#pragma pop_macro("_WIN64")
+1 -1
View File
@@ -16,7 +16,7 @@
#pragma once
#include "../boost-fix.h"
#include "../asio-fix.h"
#include <ghc/filesystem.hpp>
+17 -19
View File
@@ -16,7 +16,7 @@
#include "group.h"
#include "../boost-fix.h"
#include "../asio-fix.h"
#include <unistd.h>
#include <regex>
@@ -49,23 +49,23 @@ using namespace std::literals::chrono_literals;
* @throw std::runtime_error If another process is already listening on the
* endpoint.
*/
boost::asio::local::stream_protocol::acceptor create_acceptor_if_inactive(
boost::asio::io_context& io_context,
boost::asio::local::stream_protocol::endpoint& endpoint);
asio::local::stream_protocol::acceptor create_acceptor_if_inactive(
asio::io_context& io_context,
asio::local::stream_protocol::endpoint& endpoint);
/**
* Create a logger prefix containing the group name based on the socket path.
*/
std::string create_logger_prefix(const fs::path& socket_path);
StdIoCapture::StdIoCapture(boost::asio::io_context& io_context,
int file_descriptor)
StdIoCapture::StdIoCapture(asio::io_context& io_context, int file_descriptor)
: pipe_(io_context),
target_fd_(file_descriptor),
original_fd_copy_(dup(file_descriptor)) {
// We'll use the second element of these two file descriptors to reopen
// `file_descriptor`, and the first one to read the captured contents from
if (::pipe(pipe_fd_) != 0) {
std::cerr << "Could not create pipe" << std::endl;
throw std::system_error(errno, std::system_category());
}
@@ -171,12 +171,12 @@ void GroupBridge::handle_incoming_connections() {
void GroupBridge::accept_requests() {
group_socket_acceptor_.async_accept(
[&](const boost::system::error_code& error,
boost::asio::local::stream_protocol::socket socket) {
[&](const std::error_code& error,
asio::local::stream_protocol::socket socket) {
std::lock_guard lock(active_plugins_mutex_);
// Stop the whole process when the socket gets closed unexpectedly
if (error.failed()) {
if (error) {
logger_.log("Error while listening for incoming connections:");
logger_.log(error.message());
@@ -284,14 +284,13 @@ void GroupBridge::async_handle_events() {
[&]() { return !is_event_loop_inhibited(); });
}
boost::asio::local::stream_protocol::acceptor create_acceptor_if_inactive(
boost::asio::io_context& io_context,
boost::asio::local::stream_protocol::endpoint& endpoint) {
asio::local::stream_protocol::acceptor create_acceptor_if_inactive(
asio::io_context& io_context,
asio::local::stream_protocol::endpoint& endpoint) {
// First try to listen on the endpoint normally
try {
return boost::asio::local::stream_protocol::acceptor(io_context,
endpoint);
} catch (const boost::system::system_error&) {
return asio::local::stream_protocol::acceptor(io_context, endpoint);
} catch (const std::system_error&) {
// If this failed, then either there is a stale socket file or another
// process is already is already listening. In the last case we will
// simply throw so the other process can handle the request.
@@ -312,8 +311,7 @@ boost::asio::local::stream_protocol::acceptor create_acceptor_if_inactive(
// At this point we can remove the stale socket and start listening
fs::remove(endpoint_path);
return boost::asio::local::stream_protocol::acceptor(io_context,
endpoint);
return asio::local::stream_protocol::acceptor(io_context, endpoint);
}
}
@@ -322,10 +320,10 @@ void GroupBridge::maybe_schedule_shutdown(
std::lock_guard lock(shutdown_timer_mutex_);
shutdown_timer_.expires_after(delay);
shutdown_timer_.async_wait([this](const boost::system::error_code& error) {
shutdown_timer_.async_wait([this](const std::error_code& error) {
// A previous timer gets canceled automatically when another plugin
// exits
if (error.failed()) {
if (error) {
return;
}
+12 -12
View File
@@ -19,9 +19,9 @@
#include <atomic>
#include <thread>
#include "../boost-fix.h"
#include "../asio-fix.h"
#include <boost/asio/local/stream_protocol.hpp>
#include <asio/local/stream_protocol.hpp>
#include "../common/logging/common.h"
#include "../utils.h"
@@ -49,7 +49,7 @@ class StdIoCapture {
*
* @throw std::system_error If the pipe could not be created.
*/
StdIoCapture(boost::asio::io_context& io_context, int file_descriptor);
StdIoCapture(asio::io_context& io_context, int file_descriptor);
/**
* On cleanup, close the outgoing file descriptor from the pipe and restore
@@ -65,9 +65,9 @@ class StdIoCapture {
/**
* The pipe endpoint where all output from the original file descriptor gets
* redirected to. This can be read from like any other `Boost.Asio` stream.
* redirected to. This can be read from like any other Asio stream.
*/
boost::asio::posix::stream_descriptor pipe_;
asio::posix::stream_descriptor pipe_;
private:
/**
@@ -116,7 +116,7 @@ class GroupBridge {
* where `<wine_prefix_id>` is a numerical hash as explained in the
* `create_logger_prefix()` function in `./group.cpp`.
*
* @throw boost::system::system_error If we can't listen on the socket.
* @throw std::system_error If we can't listen on the socket.
* @throw std::system_error If the pipe could not be created.
*
* @note Creating an `GroupBridge` instance has the side effect that the
@@ -220,10 +220,10 @@ class GroupBridge {
* related operation should be run from the same thread, we can't just add
* another thread to the main IO context.
*/
boost::asio::io_context stdio_context_;
asio::io_context stdio_context_;
boost::asio::streambuf stdout_buffer_;
boost::asio::streambuf stderr_buffer_;
asio::streambuf stdout_buffer_;
asio::streambuf stderr_buffer_;
/**
* Contains a pipe used for capturing this process's STDOUT stream. Needed
* to be able to process the output generated by Wine and plugins and to be
@@ -241,12 +241,12 @@ class GroupBridge {
*/
Win32Thread stdio_handler_;
boost::asio::local::stream_protocol::endpoint group_socket_endpoint_;
asio::local::stream_protocol::endpoint group_socket_endpoint_;
/**
* The UNIX domain socket acceptor that will be used to listen for incoming
* connections to spawn new plugins within this process.
*/
boost::asio::local::stream_protocol::acceptor group_socket_acceptor_;
asio::local::stream_protocol::acceptor group_socket_acceptor_;
/**
* A map of threads that are currently hosting a plugin within this process
@@ -281,7 +281,7 @@ class GroupBridge {
*
* @see handle_plugin_run
*/
boost::asio::steady_timer shutdown_timer_;
asio::steady_timer shutdown_timer_;
/**
* A mutex to prevent two threads from simultaneously modifying the shutdown
* timer when multiple plugins exit at the same time.
+1 -1
View File
@@ -623,7 +623,7 @@ class HostCallbackDataConverter : public DefaultDataConverter {
}
Vst2EventResult send_event(
boost::asio::local::stream_protocol::socket& socket,
asio::local::stream_protocol::socket& socket,
const Vst2Event& event,
SerializationBufferBase& buffer) const override {
if (mutually_recursive_callbacks.contains(event.opcode)) {
+1 -1
View File
@@ -16,7 +16,7 @@
#pragma once
#include "../boost-fix.h"
#include "../asio-fix.h"
#include <vestige/aeffectx.h>
#include <windows.h>
+5 -5
View File
@@ -228,17 +228,17 @@ DeferredWin32Window::~DeferredWin32Window() noexcept {
// `IPlugView::~IPlugView`. Delaying this seems to be a best of both
// worlds solution that works as expected in every host I've tested.
try {
std::shared_ptr<boost::asio::steady_timer> destroy_timer =
std::make_shared<boost::asio::steady_timer>(main_context_.context_);
std::shared_ptr<asio::steady_timer> destroy_timer =
std::make_shared<asio::steady_timer>(main_context_.context_);
destroy_timer->expires_after(1s);
// Note that we capture a copy of `destroy_timer` here. This way we
// don't have to manage the timer instance ourselves as it will just
// clean itself up after this lambda gets called.
destroy_timer->async_wait(
[destroy_timer, handle = handle_, x11_connection = x11_connection_](
const boost::system::error_code& error) {
if (error.failed()) {
[destroy_timer, handle = handle_,
x11_connection = x11_connection_](const std::error_code& error) {
if (error) {
return;
}
+4 -6
View File
@@ -76,21 +76,19 @@ __cdecl
// Blocks the main thread until all plugins have exited
bridge.handle_incoming_connections();
} catch (const boost::system::system_error& error) {
} catch (const std::system_error& error) {
// If another process is already listening on the socket, we'll just
// print a message and exit quietly. This could happen if the host
// starts multiple yabridge instances that all use the same plugin group
// at the same time.
// The same error is also used if we could not create a pipe. Since that
// error is so rare, we'll just print to STDERR before that happens to
// differentiate the two cases.
std::cerr << "Another process is already listening on this group's "
"socket, connecting to the existing process:"
<< std::endl;
std::cerr << error.what() << std::endl;
return 0;
} catch (const std::system_error& error) {
std::cerr << "Could not create pipe:" << std::endl;
std::cerr << error.what() << std::endl;
return 0;
}
+2
View File
@@ -10,6 +10,7 @@ if is_64bit_system
host_64bit_deps = [
configuration_dep,
asio_dep,
boost_dep,
bitsery_dep,
function2_dep,
@@ -35,6 +36,7 @@ if with_bitbridge
host_32bit_deps = [
configuration_dep,
asio_dep,
boost_dep,
ghc_filesystem_dep,
bitsery_dep,
+2 -2
View File
@@ -175,8 +175,8 @@ void MainContext::async_handle_watchdog_timer(
// Try to keep a steady framerate, but add in delays to let other events
// get handled if the GUI message handling somehow takes very long.
watchdog_timer_.expires_at(std::chrono::steady_clock::now() + interval);
watchdog_timer_.async_wait([&](const boost::system::error_code& error) {
if (error.failed()) {
watchdog_timer_.async_wait([&](const std::error_code& error) {
if (error) {
return;
}
+12 -12
View File
@@ -16,7 +16,7 @@
#pragma once
#include "boost-fix.h"
#include "asio-fix.h"
#include <future>
#include <memory>
@@ -24,8 +24,8 @@
#include <unordered_set>
#include <windows.h>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/io_context.hpp>
#include <asio/dispatch.hpp>
#include <asio/io_context.hpp>
#include <function2/function2.hpp>
#include "../common/utils.h"
@@ -152,7 +152,7 @@ class Win32Timer {
};
/**
* A wrapper around `boost::asio::io_context()` to serve as the application's
* A wrapper around `asio::io_context()` to serve as the application's
* main IO context, run from the GUI thread. A single instance is shared for all
* plugins in a plugin group so that several important events can be handled on
* the main thread, which can be required because in the Win32 model all GUI
@@ -254,7 +254,7 @@ class MainContext {
std::packaged_task<Result()> call_fn(std::forward<F>(fn));
std::future<Result> result = call_fn.get_future();
boost::asio::dispatch(context_, std::move(call_fn));
asio::dispatch(context_, std::move(call_fn));
return result;
}
@@ -266,7 +266,7 @@ class MainContext {
*/
template <std::invocable F>
void schedule_task(F&& fn) {
boost::asio::post(context_, std::forward<F>(fn));
asio::post(context_, std::forward<F>(fn));
}
/**
@@ -294,8 +294,8 @@ class MainContext {
std::max(events_timer_.expiry() + timer_interval_,
std::chrono::steady_clock::now() + timer_interval_ / 4));
events_timer_.async_wait(
[&, handler, predicate](const boost::system::error_code& error) {
if (error.failed()) {
[&, handler, predicate](const std::error_code& error) {
if (error) {
return;
}
@@ -311,7 +311,7 @@ class MainContext {
* The raw IO context. Used to bind our sockets onto. Running things within
* this IO context should be done with the functions above.
*/
boost::asio::io_context context_;
asio::io_context context_;
private:
/**
@@ -334,7 +334,7 @@ class MainContext {
/**
* The timer used to periodically handle X11 events and Win32 messages.
*/
boost::asio::steady_timer events_timer_;
asio::steady_timer events_timer_;
/**
* The time between timer ticks in `async_handle_events`. This gets
@@ -349,7 +349,7 @@ class MainContext {
/**
* The IO context used for the watchdog described below.
*/
boost::asio::io_context watchdog_context_;
asio::io_context watchdog_context_;
/**
* The timer used to periodically check if the host processes are still
@@ -357,7 +357,7 @@ class MainContext {
* itself) when the host has exited and the sockets are somehow not closed
* yet..
*/
boost::asio::steady_timer watchdog_timer_;
asio::steady_timer watchdog_timer_;
/**
* All of the bridges we're watching as part of our watchdog. We're storing
+1 -1
View File
@@ -18,7 +18,7 @@
#include <memory>
#include "boost-fix.h"
#include "asio-fix.h"
// Use the native version of xcb
#pragma push_macro("_WIN32")