diff --git a/src/wine-host/bridges/common.cpp b/src/wine-host/bridges/common.cpp index a80d6193..1c2a66ad 100644 --- a/src/wine-host/bridges/common.cpp +++ b/src/wine-host/bridges/common.cpp @@ -34,3 +34,7 @@ void HostBridge::handle_win32_events() { DispatchMessage(&msg); } } + +void HostBridge::shutdown_if_dangling() { + // TODO: Implement +} diff --git a/src/wine-host/bridges/common.h b/src/wine-host/bridges/common.h index 76b1a5e8..90a17128 100644 --- a/src/wine-host/bridges/common.h +++ b/src/wine-host/bridges/common.h @@ -24,9 +24,11 @@ #include "../utils.h" /** - * The base for the Wine plugin host bridge interface for all plugin types. This - * only has to be able to handle Win32 and X11 events. Implementations of this - * will actually host a plugin and do all the function call forwarding. + * The base for the Wine plugin host bridge interfaces for all plugin types. + * This mostly concerns event handling, and some common setup like loggers and a + * watchdog timer to let us shut down the sockets when the native host has + * exited while the sockets are still alive. Implementations of this will + * actually host a plugin and do all the function call forwarding. */ class HostBridge { protected: @@ -86,6 +88,14 @@ class HostBridge { */ void handle_win32_events(); + /** + * Used as part of the watchdog. This will check whether the remote host + * process this bridge is connected with is still active. If it is not, then + * we'll close the sockets, which will cause this process to exit + * gracefully. + */ + void shutdown_if_dangling(); + /** * The path to the .dll being loaded in the Wine plugin host. */ diff --git a/src/wine-host/utils.cpp b/src/wine-host/utils.cpp index c795cb53..c002acb6 100644 --- a/src/wine-host/utils.cpp +++ b/src/wine-host/utils.cpp @@ -16,6 +16,10 @@ #include "utils.h" +#include "bridges/common.h" + +using namespace std::literals::chrono_literals; + uint32_t WINAPI win32_thread_trampoline(fu2::unique_function* entry_point) { (*entry_point)(); @@ -71,7 +75,18 @@ Win32Timer& Win32Timer::operator=(Win32Timer&& o) { return *this; } -MainContext::MainContext() : context(), events_timer(context) {} +MainContext::MainContext() + : context(), + events_timer(context), + watchdog_context(), + watchdog_timer(watchdog_context) { + async_handle_watchdog(); + + watchdog_handler = Win32Thread([&]() { + set_realtime_priority(false); + watchdog_context.run(); + }); +} void MainContext::run() { context.run(); @@ -85,3 +100,66 @@ void MainContext::update_timer_interval( std::chrono::steady_clock::duration new_interval) { timer_interval = new_interval; } + +MainContext::WatchdogGuard::WatchdogGuard( + HostBridge& bridge, + std::set& watched_bridges, + std::mutex& watched_bridges_mutex) + : bridge(&bridge), + watched_bridges(watched_bridges), + watched_bridges_mutex(watched_bridges_mutex) { + std::lock_guard lock(watched_bridges_mutex); + watched_bridges.insert(&bridge); +} + +MainContext::WatchdogGuard::~WatchdogGuard() { + if (is_active) { + std::lock_guard lock(watched_bridges_mutex.get()); + watched_bridges.get().erase(bridge); + } +} + +MainContext::WatchdogGuard::WatchdogGuard(WatchdogGuard&& o) + : bridge(std::move(o.bridge)), + watched_bridges(std::move(o.watched_bridges)), + watched_bridges_mutex(std::move(o.watched_bridges_mutex)) { + o.is_active = false; +} + +MainContext::WatchdogGuard& MainContext::WatchdogGuard::operator=( + WatchdogGuard&& o) { + bridge = std::move(o.bridge); + watched_bridges = std::move(o.watched_bridges); + watched_bridges_mutex = std::move(o.watched_bridges_mutex); + o.is_active = false; + + return *this; +} + +MainContext::WatchdogGuard MainContext::register_watchdog(HostBridge& bridge) { + // The guard's constructor and destructor will handle actually registering + // and unregistering the bridge from `watched_bridges` + return WatchdogGuard(bridge, watched_bridges, watched_bridges_mutex); +} + +void MainContext::async_handle_watchdog() { + // 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() + 20s); + watchdog_timer.async_wait([&](const boost::system::error_code& error) { + if (error.failed()) { + return; + } + + // When the `WatchdogGuard` field on `HostBridge` gets destroyed, that + // bridge instance will be removed from `watched_bridges`. So if our + // call to `HostBridge::shutdown_if_dangling()` shuts the plugin down, + // the instance will be removed after this lambda exits. + std::lock_guard lock(watched_bridges_mutex); + for (auto& bridge : watched_bridges) { + bridge->shutdown_if_dangling(); + } + + async_handle_watchdog(); + }); +} diff --git a/src/wine-host/utils.h b/src/wine-host/utils.h index 1362ea32..fa7ab504 100644 --- a/src/wine-host/utils.h +++ b/src/wine-host/utils.h @@ -21,6 +21,7 @@ #include #include #include +#include #ifndef NOMINMAX #define NOMINMAX @@ -34,6 +35,9 @@ #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 @@ -152,11 +156,16 @@ class Win32Timer { /** * A wrapper around `boost::asio::io_context()` to serve as the application's - * main IO context. 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 will be run from the - * application's main thread. + * 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: @@ -182,6 +191,52 @@ class MainContext { 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 @@ -262,6 +317,16 @@ class MainContext { 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. */ @@ -276,4 +341,32 @@ class MainContext { */ 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; };