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
+1
View File
@@ -17,6 +17,7 @@
#include "common.h"
#include <random>
#include <sstream>
#include "../utils.h"
+69 -74
View File
@@ -24,12 +24,12 @@
#include <bitsery/traits/vector.h>
#ifdef __WINE__
#include "../wine-host/boost-fix.h"
#include "../wine-host/asio-fix.h"
#endif
#include <boost/asio/io_context.hpp>
#include <boost/asio/local/stream_protocol.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/write.hpp>
#include <asio/io_context.hpp>
#include <asio/local/stream_protocol.hpp>
#include <asio/read.hpp>
#include <asio/write.hpp>
#include <boost/container/small_vector.hpp>
#include <ghc/filesystem.hpp>
@@ -79,51 +79,49 @@ using SerializationBuffer = boost::container::small_vector<uint8_t, N>;
*/
using SerializationBufferBase = boost::container::small_vector_base<uint8_t>;
namespace boost {
namespace asio {
template <typename PodType, typename Allocator>
inline BOOST_ASIO_MUTABLE_BUFFER buffer(
inline ASIO_MUTABLE_BUFFER buffer(
boost::container::small_vector_base<PodType, Allocator>& data)
BOOST_ASIO_NOEXCEPT {
return BOOST_ASIO_MUTABLE_BUFFER(
ASIO_NOEXCEPT {
return ASIO_MUTABLE_BUFFER(
data.size() ? &data[0] : 0, data.size() * sizeof(PodType)
#if defined(BOOST_ASIO_ENABLE_BUFFER_DEBUGGING)
#if defined(ASIO_ENABLE_BUFFER_DEBUGGING)
,
detail::buffer_debug_check<typename boost::container::small_vector_base<
PodType, Allocator>::iterator>(data.begin())
#endif // BOOST_ASIO_ENABLE_BUFFER_DEBUGGING
#endif // ASIO_ENABLE_BUFFER_DEBUGGING
);
}
// These are copied verbatim `boost::asio::buffer(std::vector<PodType,
// These are copied verbatim `asio::buffer(std::vector<PodType,
// Allocator>&, std::size_t)`, since `boost::container::small_vector` is
// compatible with the STL vector.
template <typename PodType, typename Allocator>
inline BOOST_ASIO_MUTABLE_BUFFER buffer(
inline ASIO_MUTABLE_BUFFER buffer(
boost::container::small_vector_base<PodType, Allocator>& data,
std::size_t max_size_in_bytes) BOOST_ASIO_NOEXCEPT {
return BOOST_ASIO_MUTABLE_BUFFER(
std::size_t max_size_in_bytes) ASIO_NOEXCEPT {
return ASIO_MUTABLE_BUFFER(
data.size() ? &data[0] : 0,
data.size() * sizeof(PodType) < max_size_in_bytes
? data.size() * sizeof(PodType)
: max_size_in_bytes
#if defined(BOOST_ASIO_ENABLE_BUFFER_DEBUGGING)
#if defined(ASIO_ENABLE_BUFFER_DEBUGGING)
,
detail::buffer_debug_check<typename boost::container::small_vector_base<
PodType, Allocator>::iterator>(data.begin())
#endif // BOOST_ASIO_ENABLE_BUFFER_DEBUGGING
#endif // ASIO_ENABLE_BUFFER_DEBUGGING
);
}
} // namespace asio
} // namespace boost
/**
* Serialize an object using bitsery and write it to a socket. This will write
* both the size of the serialized object and the object itself over the socket.
*
* @param socket The Boost.Asio socket to write to.
* @param socket The Asio socket to write to.
* @param object The object to write to the stream.
* @param buffer The buffer to write to. This is useful for sending audio and
* chunk data since that can vary in size by a lot.
@@ -149,10 +147,9 @@ inline void write_object(Socket& socket,
// bit bridge. This won't make any function difference aside from the
// 32-bit host application having to convert between 64 and 32 bit
// integers.
boost::asio::write(socket,
boost::asio::buffer(std::array<uint64_t, 1>{size}));
asio::write(socket, asio::buffer(std::array<uint64_t, 1>{size}));
const size_t bytes_written =
boost::asio::write(socket, boost::asio::buffer(buffer, size));
asio::write(socket, asio::buffer(buffer, size));
assert(bytes_written == size);
}
@@ -171,7 +168,7 @@ inline void write_object(Socket& socket, const T& object) {
* Deserialize an object by reading it from a socket. This should be used
* together with `write_object`. This will block until the object is available.
*
* @param socket The Boost.Asio socket to read from.
* @param socket The Asio socket to read from.
* @param object The object to serialize into. There are also overrides that
* create a new default initialized `T`
* @param buffer The buffer to read into. This is useful for sending audio and
@@ -180,7 +177,7 @@ inline void write_object(Socket& socket, const T& object) {
* @return The deserialized object.
*
* @throw std::runtime_error If the conversion to an object was not successful.
* @throw boost::system::system_error If the socket is closed or gets closed
* @throw std::system_error If the socket is closed or gets closed
* while reading.
*
* @relates write_object
@@ -191,18 +188,17 @@ inline T& read_object(Socket& socket,
SerializationBufferBase& buffer) {
// See the note above on the use of `uint64_t` instead of `size_t`
std::array<uint64_t, 1> message_length;
boost::asio::read(socket, boost::asio::buffer(message_length),
boost::asio::transfer_exactly(sizeof(message_length)));
asio::read(socket, asio::buffer(message_length),
asio::transfer_exactly(sizeof(message_length)));
// Make sure the buffer is large enough
const size_t size = message_length[0];
buffer.resize(size);
// `boost::asio::read/write` will handle all the packet splitting and
// `asio::read/write` will handle all the packet splitting and
// merging for us, since local domain sockets have packet limits somewhere
// in the hundreds of kilobytes
boost::asio::read(socket, boost::asio::buffer(buffer),
boost::asio::transfer_exactly(size));
asio::read(socket, asio::buffer(buffer), asio::transfer_exactly(size));
auto [_, success] =
bitsery::quickDeserialization<InputAdapter<SerializationBufferBase>>(
@@ -371,8 +367,8 @@ class SocketHandler {
*
* @see Sockets::connect
*/
SocketHandler(boost::asio::io_context& io_context,
boost::asio::local::stream_protocol::endpoint endpoint,
SocketHandler(asio::io_context& io_context,
asio::local::stream_protocol::endpoint endpoint,
bool listen)
: endpoint_(endpoint), socket_(io_context) {
if (listen) {
@@ -397,13 +393,13 @@ class SocketHandler {
/**
* Close the socket. Both sides that are actively listening will be thrown a
* `boost::system_error` when this happens.
* `std::system_error` when this happens.
*/
void close() {
// The shutdown can fail when the socket is already closed
boost::system::error_code err;
socket_.shutdown(
boost::asio::local::stream_protocol::socket::shutdown_both, err);
std::error_code err;
socket_.shutdown(asio::local::stream_protocol::socket::shutdown_both,
err);
socket_.close();
}
@@ -414,7 +410,7 @@ class SocketHandler {
* @param buffer The buffer to use for the serialization. This is used to
* prevent excess allocations when sending audio.
*
* @throw boost::system::system_error If the socket is closed or gets closed
* @throw std::system_error If the socket is closed or gets closed
* during sending.
*
* @warning This operation is not atomic, and calling this function with the
@@ -454,7 +450,7 @@ class SocketHandler {
*
* @throw std::runtime_error If the conversion to an object was not
* successful.
* @throw boost::system::system_error If the socket is closed or gets closed
* @throw std::system_error If the socket is closed or gets closed
* while reading.
*
* @note This function can safely be called within the lambda of
@@ -513,7 +509,7 @@ class SocketHandler {
receive_single<T>(object, buffer);
callback(object, buffer);
} catch (const boost::system::system_error&) {
} catch (const std::system_error&) {
// This happens when the sockets got closed because the plugin
// is being shut down
break;
@@ -522,14 +518,14 @@ class SocketHandler {
}
private:
boost::asio::local::stream_protocol::endpoint endpoint_;
boost::asio::local::stream_protocol::socket socket_;
asio::local::stream_protocol::endpoint endpoint_;
asio::local::stream_protocol::socket socket_;
/**
* Will be used in `connect()` on the listening side to establish the
* connection.
*/
std::optional<boost::asio::local::stream_protocol::acceptor> acceptor_;
std::optional<asio::local::stream_protocol::acceptor> acceptor_;
};
/**
@@ -572,8 +568,8 @@ class AdHocSocketHandler {
*
* @see Sockets::connect
*/
AdHocSocketHandler(boost::asio::io_context& io_context,
boost::asio::local::stream_protocol::endpoint endpoint,
AdHocSocketHandler(asio::io_context& io_context,
asio::local::stream_protocol::endpoint endpoint,
bool listen)
: io_context_(io_context), endpoint_(endpoint), socket_(io_context) {
if (listen) {
@@ -606,13 +602,13 @@ class AdHocSocketHandler {
/**
* Close the socket. Both sides that are actively listening will be thrown a
* `boost::system_error` when this happens.
* `std::system_error` when this happens.
*/
void close() {
// The shutdown can fail when the socket is already closed
boost::system::error_code err;
socket_.shutdown(
boost::asio::local::stream_protocol::socket::shutdown_both, err);
std::error_code err;
socket_.shutdown(asio::local::stream_protocol::socket::shutdown_both,
err);
socket_.close();
while (currently_listening_) {
@@ -637,15 +633,15 @@ class AdHocSocketHandler {
* socket. This is either the primary `socket`, or a new ad hock socket if
* this function is currently being called from another thread.
*/
template <std::invocable<boost::asio::local::stream_protocol::socket&> F>
std::invoke_result_t<F, boost::asio::local::stream_protocol::socket&> send(
template <std::invocable<asio::local::stream_protocol::socket&> F>
std::invoke_result_t<F, asio::local::stream_protocol::socket&> send(
F&& callback) {
// A bit of template and constexpr nastiness to allow us to either
// return a value from the callback (for when writing the response to a
// new object) or to return void (when we deserialize into an existing
// object)
constexpr bool returns_void = std::is_void_v<std::invoke_result_t<
F, boost::asio::local::stream_protocol::socket&>>;
constexpr bool returns_void = std::is_void_v<
std::invoke_result_t<F, asio::local::stream_protocol::socket&>>;
// XXX: Maybe at some point we should benchmark how often this
// ad hoc socket spawning mechanism gets used. If some hosts
@@ -667,12 +663,12 @@ class AdHocSocketHandler {
}
} else {
try {
boost::asio::local::stream_protocol::socket secondary_socket(
asio::local::stream_protocol::socket secondary_socket(
io_context_);
secondary_socket.connect(endpoint_);
return callback(secondary_socket);
} catch (const boost::system::system_error&) {
} catch (const std::system_error&) {
// So, what do we do when noone is listening on the endpoint
// yet? This can happen with plugin groups when the Wine
// host process does an `audioMaster()` call before the
@@ -721,8 +717,8 @@ class AdHocSocketHandler {
* same thing as `primary_callback`, but secondary sockets may need some
* different handling.
*/
template <std::invocable<boost::asio::local::stream_protocol::socket&> F,
std::invocable<boost::asio::local::stream_protocol::socket&> G>
template <std::invocable<asio::local::stream_protocol::socket&> F,
std::invocable<asio::local::stream_protocol::socket&> G>
void receive_multi(std::optional<std::reference_wrapper<Logger>> logger,
F&& primary_callback,
G&& secondary_callback) {
@@ -738,7 +734,7 @@ class AdHocSocketHandler {
// thread to handle the request. When `socket` closes and this loop
// breaks, the listener and any still active threads will be cleaned up
// before this function exits.
boost::asio::io_context secondary_context{};
asio::io_context secondary_context{};
// The previous acceptor has already been shut down by
// `AdHocSocketHandler::connect()`
@@ -751,21 +747,21 @@ class AdHocSocketHandler {
std::mutex active_secondary_requests_mutex{};
accept_requests(
*acceptor_, logger,
[&](boost::asio::local::stream_protocol::socket secondary_socket) {
[&](asio::local::stream_protocol::socket secondary_socket) {
const size_t request_id = next_request_id.fetch_add(1);
// We have to make sure to keep moving these sockets into the
// threads that will handle them
std::lock_guard lock(active_secondary_requests_mutex);
active_secondary_requests[request_id] = Thread(
[&, request_id](boost::asio::local::stream_protocol::socket
secondary_socket) {
[&, request_id](
asio::local::stream_protocol::socket secondary_socket) {
secondary_callback(secondary_socket);
// When we have processed this request, we'll join the
// thread again with the thread that's handling
// `secondary_context`
boost::asio::post(secondary_context, [&, request_id]() {
asio::post(secondary_context, [&, request_id]() {
std::lock_guard lock(
active_secondary_requests_mutex);
@@ -791,7 +787,7 @@ class AdHocSocketHandler {
while (true) {
try {
primary_callback(socket_);
} catch (const boost::system::system_error&) {
} catch (const std::system_error&) {
// This happens when the sockets got closed because the plugin
// is being shut down
break;
@@ -814,7 +810,7 @@ class AdHocSocketHandler {
*
* @overload
*/
template <std::invocable<boost::asio::local::stream_protocol::socket&> F>
template <std::invocable<asio::local::stream_protocol::socket&> F>
void receive_multi(std::optional<std::reference_wrapper<Logger>> logger,
F&& callback) {
receive_multi(logger, callback, std::forward<F>(callback));
@@ -831,16 +827,15 @@ class AdHocSocketHandler {
* should only be passed on the plugin side.
* @param callback A function that handles the new socket connection.
*/
template <std::invocable<boost::asio::local::stream_protocol::socket> F>
void accept_requests(
boost::asio::local::stream_protocol::acceptor& acceptor,
std::optional<std::reference_wrapper<Logger>> logger,
F&& callback) {
template <std::invocable<asio::local::stream_protocol::socket> F>
void accept_requests(asio::local::stream_protocol::acceptor& acceptor,
std::optional<std::reference_wrapper<Logger>> logger,
F&& callback) {
acceptor.async_accept(
[&, logger, callback](
const boost::system::error_code& error,
boost::asio::local::stream_protocol::socket secondary_socket) {
if (error.failed()) {
const std::error_code& error,
asio::local::stream_protocol::socket secondary_socket) {
if (error) {
// On the Wine side it's expected that the primary socket
// connection will be dropped during shutdown, so we can
// silently ignore any related socket errors on the Wine
@@ -865,10 +860,10 @@ class AdHocSocketHandler {
* bound to this context. In `receive_multi()` we'll create a new IO context
* since we want to do all listening there on a dedicated thread.
*/
boost::asio::io_context& io_context_;
asio::io_context& io_context_;
boost::asio::local::stream_protocol::endpoint endpoint_;
boost::asio::local::stream_protocol::socket socket_;
asio::local::stream_protocol::endpoint endpoint_;
asio::local::stream_protocol::socket socket_;
/**
* This acceptor will be used once synchronously on the listening side
@@ -880,7 +875,7 @@ class AdHocSocketHandler {
* but all additional incoming connections of course have to be listened for
* on the plugin side.
*/
std::optional<boost::asio::local::stream_protocol::acceptor> acceptor_;
std::optional<asio::local::stream_protocol::acceptor> acceptor_;
/**
* After the socket gets closed, we do some cleanup at the end of
+1 -1
View File
@@ -73,7 +73,7 @@ intptr_t DefaultDataConverter::return_value(const int /*opcode*/,
}
Vst2EventResult DefaultDataConverter::send_event(
boost::asio::local::stream_protocol::socket& socket,
asio::local::stream_protocol::socket& socket,
const Vst2Event& event,
SerializationBufferBase& buffer) const {
write_object(socket, event, buffer);
+8 -8
View File
@@ -86,7 +86,7 @@ class DefaultDataConverter {
* specific opcodes to allow mutually recursive calling sequences.
*/
virtual Vst2EventResult send_event(
boost::asio::local::stream_protocol::socket& socket,
asio::local::stream_protocol::socket& socket,
const Vst2Event& event,
SerializationBufferBase& buffer) const;
};
@@ -137,8 +137,8 @@ class Vst2EventHandler : public AdHocSocketHandler<Thread> {
*
* @see Sockets::connect
*/
Vst2EventHandler(boost::asio::io_context& io_context,
boost::asio::local::stream_protocol::endpoint endpoint,
Vst2EventHandler(asio::io_context& io_context,
asio::local::stream_protocol::endpoint endpoint,
bool listen)
: AdHocSocketHandler<Thread>(io_context, endpoint, listen) {}
@@ -206,7 +206,7 @@ class Vst2EventHandler : public AdHocSocketHandler<Thread> {
// that potentially need to have their responses handled on the same
// calling thread (i.e. mutual recursion).
const Vst2EventResult response = this->send(
[&](boost::asio::local::stream_protocol::socket& socket) {
[&](asio::local::stream_protocol::socket& socket) {
return data_converter.send_event(socket, event,
serialization_buffer());
});
@@ -250,7 +250,7 @@ class Vst2EventHandler : public AdHocSocketHandler<Thread> {
// Reading, processing, and writing back event data from the sockets
// works in the same way regardless of which socket we're using
const auto process_event =
[&](boost::asio::local::stream_protocol::socket& socket,
[&](asio::local::stream_protocol::socket& socket,
bool on_main_thread) {
SerializationBufferBase& buffer = serialization_buffer();
@@ -276,10 +276,10 @@ class Vst2EventHandler : public AdHocSocketHandler<Thread> {
this->receive_multi(
logging ? std::optional(std::ref(logging->first.logger_))
: std::nullopt,
[&](boost::asio::local::stream_protocol::socket& socket) {
[&](asio::local::stream_protocol::socket& socket) {
process_event(socket, true);
},
[&](boost::asio::local::stream_protocol::socket& socket) {
[&](asio::local::stream_protocol::socket& socket) {
process_event(socket, false);
});
}
@@ -342,7 +342,7 @@ class Vst2Sockets final : public Sockets {
*
* @see Vst2Sockets::connect
*/
Vst2Sockets(boost::asio::io_context& io_context,
Vst2Sockets(asio::io_context& io_context,
const ghc::filesystem::path& endpoint_base_dir,
bool listen)
: Sockets(endpoint_base_dir),
+6 -6
View File
@@ -57,8 +57,8 @@ class Vst3MessageHandler : public AdHocSocketHandler<Thread> {
*
* @see Sockets::connect
*/
Vst3MessageHandler(boost::asio::io_context& io_context,
boost::asio::local::stream_protocol::endpoint endpoint,
Vst3MessageHandler(asio::io_context& io_context,
asio::local::stream_protocol::endpoint endpoint,
bool listen)
: AdHocSocketHandler<Thread>(io_context, endpoint, listen) {}
@@ -138,7 +138,7 @@ class Vst3MessageHandler : public AdHocSocketHandler<Thread> {
// messages from arriving out of order. `AdHocSocketHandler::send()`
// will either use a long-living primary socket, or if that's currently
// in use it will spawn a new socket for us.
this->send([&](boost::asio::local::stream_protocol::socket& socket) {
this->send([&](asio::local::stream_protocol::socket& socket) {
write_object(socket, Request(object), buffer);
read_object<TResponse>(socket, response_object, buffer);
});
@@ -205,7 +205,7 @@ class Vst3MessageHandler : public AdHocSocketHandler<Thread> {
// we receive works in the same way regardless of which socket we're
// using
const auto process_message =
[&](boost::asio::local::stream_protocol::socket& socket) {
[&](asio::local::stream_protocol::socket& socket) {
// The persistent buffer is only used when the
// `persistent_buffers` template value is enabled, but we'll
// always use the thread local persistent object. Because of
@@ -310,7 +310,7 @@ class Vst3Sockets final : public Sockets {
*
* @see Vst3Sockets::connect
*/
Vst3Sockets(boost::asio::io_context& io_context,
Vst3Sockets(asio::io_context& io_context,
const ghc::filesystem::path& endpoint_base_dir,
bool listen)
: Sockets(endpoint_base_dir),
@@ -513,7 +513,7 @@ class Vst3Sockets final : public Sockets {
audio_processor_buffer);
}
boost::asio::io_context& io_context_;
asio::io_context& io_context_;
/**
* Every `IAudioProcessor` or `IComponent` instance (which likely implements