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:
Robbert van der Helm
2021-05-01 16:45:37 +02:00
parent 167784f93b
commit 0f2db64aeb
4 changed files with 194 additions and 9 deletions
+98 -5
View File
@@ -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;
};