mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-07 03:50:11 +02:00
Add the pieces for a watchdog for dangling plugins
Once we register our plugins here, we should be able to gracefully shut down any plugins whose remote process got killed.
This commit is contained in:
@@ -34,3 +34,7 @@ void HostBridge::handle_win32_events() {
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
void HostBridge::shutdown_if_dangling() {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
+79
-1
@@ -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<void()>* 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<HostBridge*>& 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();
|
||||
});
|
||||
}
|
||||
|
||||
+98
-5
@@ -21,6 +21,7 @@
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
|
||||
#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<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
|
||||
@@ -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<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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user