// 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 . #pragma once #include "boost-fix.h" #include #include #include #include #ifndef NOMINMAX #define NOMINMAX #define WINE_NOWINSOCK #endif #include #include #include #include #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*` pointer to a function * pointer, great. */ uint32_t WINAPI win32_thread_trampoline(fu2::unique_function* 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 Win32Thread(Function&& f, Args&&... args) : handle( CreateThread( nullptr, 0, reinterpret_cast( 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( [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, 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 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& 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> watched_bridges; std::reference_wrapper 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 std::future run_in_context(F fn) { std::packaged_task call_fn(std::move(fn)); std::future 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 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 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 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; };