mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-16 05:33:07 +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);
|
DispatchMessage(&msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HostBridge::shutdown_if_dangling() {
|
||||||
|
// TODO: Implement
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,9 +24,11 @@
|
|||||||
#include "../utils.h"
|
#include "../utils.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base for the Wine plugin host bridge interface for all plugin types. This
|
* The base for the Wine plugin host bridge interfaces for all plugin types.
|
||||||
* only has to be able to handle Win32 and X11 events. Implementations of this
|
* This mostly concerns event handling, and some common setup like loggers and a
|
||||||
* will actually host a plugin and do all the function call forwarding.
|
* 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 {
|
class HostBridge {
|
||||||
protected:
|
protected:
|
||||||
@@ -86,6 +88,14 @@ class HostBridge {
|
|||||||
*/
|
*/
|
||||||
void handle_win32_events();
|
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.
|
* The path to the .dll being loaded in the Wine plugin host.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+79
-1
@@ -16,6 +16,10 @@
|
|||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
#include "bridges/common.h"
|
||||||
|
|
||||||
|
using namespace std::literals::chrono_literals;
|
||||||
|
|
||||||
uint32_t WINAPI
|
uint32_t WINAPI
|
||||||
win32_thread_trampoline(fu2::unique_function<void()>* entry_point) {
|
win32_thread_trampoline(fu2::unique_function<void()>* entry_point) {
|
||||||
(*entry_point)();
|
(*entry_point)();
|
||||||
@@ -71,7 +75,18 @@ Win32Timer& Win32Timer::operator=(Win32Timer&& o) {
|
|||||||
return *this;
|
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() {
|
void MainContext::run() {
|
||||||
context.run();
|
context.run();
|
||||||
@@ -85,3 +100,66 @@ void MainContext::update_timer_interval(
|
|||||||
std::chrono::steady_clock::duration new_interval) {
|
std::chrono::steady_clock::duration new_interval) {
|
||||||
timer_interval = 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 <future>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
#ifndef NOMINMAX
|
#ifndef NOMINMAX
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
@@ -34,6 +35,9 @@
|
|||||||
|
|
||||||
#include "../common/utils.h"
|
#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()`
|
* A proxy function that calls `Win32Thread::entry_point` since `CreateThread()`
|
||||||
* is not usable with lambdas directly. Calling the passed function will invoke
|
* 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
|
* 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
|
* main IO context, run from the GUI thread. A single instance is shared for all
|
||||||
* group so that several important events can be handled on the main thread,
|
* plugins in a plugin group so that several important events can be handled on
|
||||||
* which can be required because in the Win32 model all GUI related operations
|
* the main thread, which can be required because in the Win32 model all GUI
|
||||||
* have to be handled from the same thread. This will be run from the
|
* related operations have to be handled from the same thread.
|
||||||
* application's main 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 {
|
class MainContext {
|
||||||
public:
|
public:
|
||||||
@@ -182,6 +191,52 @@ class MainContext {
|
|||||||
void update_timer_interval(
|
void update_timer_interval(
|
||||||
std::chrono::steady_clock::duration new_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
|
* 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
|
* 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;
|
boost::asio::io_context context;
|
||||||
|
|
||||||
private:
|
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.
|
* 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::steady_clock::duration timer_interval =
|
||||||
std::chrono::milliseconds(1000) / 60;
|
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