mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-07 03:50:11 +02:00
0f2db64aeb
Once we register our plugins here, we should be able to gracefully shut down any plugins whose remote process got killed.
373 lines
14 KiB
C++
373 lines
14 KiB
C++
// yabridge: a Wine VST bridge
|
|
// Copyright (C) 2020-2021 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
|
|
|
|
#include "boost-fix.h"
|
|
|
|
#include <future>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <set>
|
|
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#define WINE_NOWINSOCK
|
|
#endif
|
|
#include <windows.h>
|
|
|
|
#include <boost/asio/dispatch.hpp>
|
|
#include <boost/asio/io_context.hpp>
|
|
#include <function2/function2.hpp>
|
|
|
|
#include "../common/utils.h"
|
|
|
|
// Forward declaration for use in our watchdog in `MainContext`
|
|
class HostBridge;
|
|
|
|
/**
|
|
* A proxy function that calls `Win32Thread::entry_point` since `CreateThread()`
|
|
* is not usable with lambdas directly. Calling the passed function will invoke
|
|
* the lambda with the arguments passed during `Win32Thread`'s constructor. This
|
|
* function deallocates the function after it's finished executing.
|
|
*
|
|
* We can't store the function pointer in the `Win32Thread` object because
|
|
* moving a `Win32Thread` object would then cause issues.
|
|
*
|
|
* @param entry_point A `fu2::unique_function<void()>*` pointer to a function
|
|
* pointer, great.
|
|
*/
|
|
uint32_t WINAPI
|
|
win32_thread_trampoline(fu2::unique_function<void()>* entry_point);
|
|
|
|
/**
|
|
* A simple RAII wrapper around the Win32 thread API that imitates
|
|
* `std::jthread`, including implicit joining (or waiting, since this is Win32)
|
|
* on destruction.
|
|
*
|
|
* `std::thread` uses pthreads directly in Winelib (since this is technically a
|
|
* regular Linux application). This means that when using
|
|
* `std::thread`/`std::jthread` directly, some thread local information that
|
|
* `CreateThread()` would normally set does not get initialized. This could then
|
|
* lead to memory errors. This wrapper aims to be equivalent to `std::jthread`,
|
|
* but using the Win32 API instead.
|
|
*
|
|
* @note This should be used instead of `std::thread` or `std::jthread` whenever
|
|
* the thread directly calls third party library code, i.e. `LoadLibrary()`,
|
|
* `FreeLibrary()`, the plugin's entry point, or any of the `AEffect::*()`
|
|
* functions.
|
|
*/
|
|
class Win32Thread {
|
|
public:
|
|
/**
|
|
* Constructor that does not start any thread yet.
|
|
*/
|
|
Win32Thread();
|
|
|
|
/**
|
|
* Constructor that immediately starts running the thread. This works
|
|
* equivalently to `std::jthread`.
|
|
*
|
|
* @param entry_point The thread entry point that should be run.
|
|
* @param parameter The parameter passed to the entry point function.
|
|
*/
|
|
template <typename Function, typename... Args>
|
|
Win32Thread(Function&& f, Args&&... args)
|
|
: handle(
|
|
CreateThread(
|
|
nullptr,
|
|
0,
|
|
reinterpret_cast<LPTHREAD_START_ROUTINE>(
|
|
win32_thread_trampoline),
|
|
// `std::function` does not support functions with move
|
|
// captures the function has to be copy-constructable.
|
|
// Function2's unique_function lets us capture and move our
|
|
// arguments to the lambda so we don't end up with dangling
|
|
// references.
|
|
new fu2::unique_function<void()>(
|
|
[f = std::move(f), ... args = std::move(args)]() mutable {
|
|
f(std::move(args)...);
|
|
}),
|
|
0,
|
|
nullptr),
|
|
CloseHandle) {}
|
|
|
|
/**
|
|
* Join (or wait on, since this is WIn32) the thread on shutdown, just like
|
|
* `std::jthread` does.
|
|
*/
|
|
~Win32Thread();
|
|
|
|
Win32Thread(const Win32Thread&) = delete;
|
|
Win32Thread& operator=(const Win32Thread&) = delete;
|
|
|
|
Win32Thread(Win32Thread&&);
|
|
Win32Thread& operator=(Win32Thread&&);
|
|
|
|
private:
|
|
// FIXME: This emits `-Wignored-attributes` as of Wine 5.22
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wignored-attributes"
|
|
|
|
/**
|
|
* The handle for the thread that is running, will be a null pointer if this
|
|
* class was constructed with the default constructor.
|
|
*/
|
|
std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&CloseHandle)>
|
|
handle;
|
|
|
|
#pragma GCC diagnostic pop
|
|
};
|
|
|
|
/**
|
|
* A simple RAII wrapper around `SetTimer`. Does not support timer procs since
|
|
* we don't use them.
|
|
*/
|
|
class Win32Timer {
|
|
public:
|
|
Win32Timer();
|
|
Win32Timer(HWND window_handle, size_t timer_id, unsigned int interval_ms);
|
|
|
|
~Win32Timer();
|
|
|
|
Win32Timer(const Win32Timer&) = delete;
|
|
Win32Timer& operator=(const Win32Timer&) = delete;
|
|
|
|
Win32Timer(Win32Timer&&);
|
|
Win32Timer& operator=(Win32Timer&&);
|
|
|
|
private:
|
|
HWND window_handle;
|
|
std::optional<size_t> timer_id;
|
|
};
|
|
|
|
/**
|
|
* A wrapper around `boost::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
|
|
* related operations have to be handled from the same thread.
|
|
*
|
|
* This also spawns a second IO context in its own thread, which will be used as
|
|
* a watchdog to shutdown a plugin instance's sockets when the process that
|
|
* spawned it is no longer active. This approach also works with plugin groups
|
|
* since closing a plugin's sockets will only cause that one plugin to
|
|
* terminate.
|
|
*/
|
|
class MainContext {
|
|
public:
|
|
MainContext();
|
|
|
|
/**
|
|
* Run the IO context. This rest of this class assumes that this is only
|
|
* done from a single thread.
|
|
*/
|
|
void run();
|
|
|
|
/**
|
|
* Drop all future work from the IO context. This does not necessarily mean
|
|
* that the thread that called `main_context.run()` immediatly returns.
|
|
*/
|
|
void stop();
|
|
|
|
/**
|
|
* Set a new timer interval. We'll do this whenever a new plugin loads,
|
|
* because we can't know in advance what the plugin's frame rate option is
|
|
* set to.
|
|
*/
|
|
void update_timer_interval(
|
|
std::chrono::steady_clock::duration new_interval);
|
|
|
|
/**
|
|
* The RAII guard used to register and unregister host bridge instances from
|
|
* our watchdog.
|
|
*/
|
|
class WatchdogGuard {
|
|
public:
|
|
WatchdogGuard(HostBridge& bridge,
|
|
std::set<HostBridge*>& watched_bridges,
|
|
std::mutex& watched_bridges_mutex);
|
|
~WatchdogGuard();
|
|
|
|
WatchdogGuard(const WatchdogGuard&) = delete;
|
|
WatchdogGuard& operator=(const WatchdogGuard&) = delete;
|
|
|
|
WatchdogGuard(WatchdogGuard&& o);
|
|
WatchdogGuard& operator=(WatchdogGuard&& o);
|
|
|
|
private:
|
|
/**
|
|
* Used to facilitate moves.
|
|
*/
|
|
bool is_active = true;
|
|
|
|
/**
|
|
* The bridge that we will add to the watchdog list when this object
|
|
* gets created, and that we'll remove from the list again when this
|
|
* object gets destroyed.
|
|
*/
|
|
HostBridge* bridge;
|
|
|
|
// References to the same two fields on `MainContext`, so we don't have
|
|
// to use `friend`
|
|
std::reference_wrapper<std::set<HostBridge*>> watched_bridges;
|
|
std::reference_wrapper<std::mutex> watched_bridges_mutex;
|
|
};
|
|
|
|
/**
|
|
* Register a bridge instance for our watchdog. We'll periodically check if
|
|
* the remote (native) host process that should be connected to the bridge
|
|
* instance is still alive, and we'll shut down the bridge if it is not to
|
|
* prevent dangling processes. The returned guard should be stored as a
|
|
* field in `HostBridge`, and the watchdog will automatically be
|
|
* unregistered once this guard drops from scope.
|
|
*/
|
|
WatchdogGuard register_watchdog(HostBridge& bridge);
|
|
|
|
/**
|
|
* Asynchronously execute a function inside of this main IO context and
|
|
* return the results as a future. This is used to make sure that operations
|
|
* that may involve the Win32 message loop are all run from the same thread.
|
|
*/
|
|
template <typename T, typename F>
|
|
std::future<T> run_in_context(F fn) {
|
|
std::packaged_task<T()> call_fn(std::move(fn));
|
|
std::future<T> response = call_fn.get_future();
|
|
boost::asio::dispatch(context, std::move(call_fn));
|
|
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Run a task within the IO context. The difference with `run_in_context()`
|
|
* is that this version does not guarantee that it's going to be executed as
|
|
* soon as possible, and thus we also won't return a future.
|
|
*/
|
|
template <typename F>
|
|
void schedule_task(F fn) {
|
|
boost::asio::post(context, std::move(fn));
|
|
}
|
|
|
|
/**
|
|
* Start a timer to handle events on a user configurable interval. The
|
|
* interval is controllable through the `frame_rate` option and defaults to
|
|
* 60 updates per second.
|
|
*
|
|
* @param handler The function that should be executed in the IO context
|
|
* when the timer ticks. This should be a function that handles both the
|
|
* X11 events and the Win32 message loop.
|
|
* @param predicate A function returning a boolean to indicate whether
|
|
* `handler` should be run. If this returns `false`, then the current
|
|
* event loop cycle will be skipped. This is used to prevent the Win32
|
|
* message loop from being run when there are partially initialized
|
|
* plugins. So far the VST2 versions of T-RackS 5 are the only plugins
|
|
* where this has been an issue as those plugins have a race condition
|
|
* that will cause them to stall indefinitely in this situation, but who
|
|
* knows which other plugins exert similar behaviour.
|
|
*/
|
|
template <typename F, typename P>
|
|
void async_handle_events(F handler, P predicate) {
|
|
// 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.
|
|
events_timer.expires_at(
|
|
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()) {
|
|
return;
|
|
}
|
|
|
|
if (predicate()) {
|
|
// NOTE: These periodic callbacks should not be able to
|
|
// interrupt other threads that are actively
|
|
// processing audio. For me personally having the GUI
|
|
// open makes absolutely zero difference on DSP usage
|
|
// (as it should), but for some others it does have an
|
|
// impact.
|
|
// TODO: Benchmark this further on a properly configured
|
|
// system, see if it does not increase average load
|
|
// because of the rapid scheduling switching.
|
|
set_realtime_priority(false);
|
|
handler();
|
|
set_realtime_priority(true);
|
|
}
|
|
|
|
async_handle_events(handler, predicate);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
private:
|
|
/**
|
|
* Start a timer to periodically check whether the host processes belong to
|
|
* all active plugin bridges are still alive. We will shut down the plugin
|
|
* instances where this is not the case, so that this process can gracefully
|
|
* terminate. In some cases Unix Domain Sockets are left in a state where
|
|
* it's impossible to tell that the remote isn't alive anymore, and where
|
|
* `recv()` will just hang indefinitely. We use this watchdog to avoid this.
|
|
*/
|
|
void async_handle_watchdog();
|
|
|
|
/**
|
|
* The timer used to periodically handle X11 events and Win32 messages.
|
|
*/
|
|
boost::asio::steady_timer events_timer;
|
|
|
|
/**
|
|
* The time between timer ticks in `async_handle_events`. This gets
|
|
* initialized at 60 ticks per second, and when a plugin load we'll update
|
|
* this value based on the plugin's `frame_rate` option.
|
|
*
|
|
* @see update_timer_interval
|
|
*/
|
|
std::chrono::steady_clock::duration timer_interval =
|
|
std::chrono::milliseconds(1000) / 60;
|
|
|
|
/**
|
|
* The IO context used for the watchdog described below.
|
|
*/
|
|
boost::asio::io_context watchdog_context;
|
|
|
|
/**
|
|
* The timer used to periodically check if the host processes are still
|
|
* active, so we can shut down a plugin's sockets (and with that the plugin
|
|
* itself) when the host has exited and the sockets are somehow not closed
|
|
* yet..
|
|
*/
|
|
boost::asio::steady_timer watchdog_timer;
|
|
|
|
/**
|
|
* All of the bridges we're watching as part of our watchdog. We're storing
|
|
* pointers for efficiency's sake, since reference wrappers don't implement
|
|
* any comparison operators.
|
|
*/
|
|
std::set<HostBridge*> watched_bridges;
|
|
std::mutex watched_bridges_mutex;
|
|
|
|
/**
|
|
* The thread where we run our watchdog timer, to shut down plugins after
|
|
* the native plugin host process they're supposed to be connected to has
|
|
* died.
|
|
*/
|
|
Win32Thread watchdog_handler;
|
|
};
|