Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions include/go_plugin/server.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#pragma once

#include <condition_variable>
#include <memory>
#include <mutex>
#include <ostream>
#include <string>
#include <sys/types.h>
#include <thread>
#include <vector>

#include <grpcpp/grpcpp.h>
Expand Down Expand Up @@ -85,8 +89,8 @@ class PluginServer {
bool Start(std::string* error = nullptr);

/**
* Blocks until the server stops (i.e. until the host process exits or
* Shutdown() is called).
* Blocks until the server stops (i.e. until Shutdown() is called or the
* host process exits — see the parent-death watchdog started by Start()).
*/
void Wait();

Expand All @@ -97,9 +101,24 @@ class PluginServer {
int port() const { return port_; }

private:
// Watches for the host (parent) process disappearing. go-plugin hosts stop a
// plugin by killing it, but when the host itself dies unexpectedly (e.g. it
// is SIGKILLed) nothing signals the plugin and Wait() would block forever,
// orphaning the process. The watchdog detects reparenting (getppid changes)
// and calls Shutdown(), mirroring the Go go-plugin server's built-in
// orphan protection. Started by Start(), stopped by Shutdown()/destruction.
void StartParentWatchdog();
void StopParentWatchdog();

ServeConfig config_;
std::unique_ptr<grpc::Server> server_;
int port_ = 0;

std::thread parent_watchdog_;
std::mutex watchdog_mu_;
std::condition_variable watchdog_cv_;
bool watchdog_stop_ = false;
pid_t host_pid_ = 0;
};

/** Returned by the Serve() convenience function. */
Expand Down
45 changes: 45 additions & 0 deletions src/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <algorithm>
#include <cerrno>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <iostream>
Expand Down Expand Up @@ -90,6 +91,7 @@ int PickPort() {
PluginServer::PluginServer(ServeConfig config) : config_(std::move(config)) {}

PluginServer::~PluginServer() {
StopParentWatchdog();
if (server_) {
server_->Shutdown();
server_->Wait();
Expand All @@ -103,6 +105,11 @@ bool PluginServer::Start(std::string *out_error) {
return false;
};

// Record the launching host's pid up front, before any startup work could let
// it reparent us — this is the baseline the parent-death watchdog compares
// against. The watchdog itself only starts once startup succeeds (below).
host_pid_ = getppid();

// ── 1. Magic cookie check ────────────────────────────────────────────
if (!ValidateMagicCookie(config_.handshake)) {
return fail("magic cookie mismatch: plugin not invoked by a go-plugin host "
Expand Down Expand Up @@ -160,6 +167,10 @@ bool PluginServer::Start(std::string *out_error) {
<< "|" << server_cert_b64 << "\n";
out.flush();

// Startup succeeded and Wait() is about to become the plugin's blocking point:
// start watching the host (host_pid_, captured at the top of Start()).
StartParentWatchdog();

return true;
}

Expand All @@ -169,10 +180,44 @@ void PluginServer::Wait() {
}

void PluginServer::Shutdown() {
// Stop (and, for external callers, join) the watchdog before tearing down the
// server, so no watchdog thread outlives Shutdown() for an external caller.
StopParentWatchdog();
if (server_)
server_->Shutdown();
}
Comment thread
devgianlu marked this conversation as resolved.

void PluginServer::StartParentWatchdog() {
parent_watchdog_ = std::thread([this]() {
std::unique_lock<std::mutex> lock(watchdog_mu_);
while (!watchdog_stop_) {
// Reparenting (parent pid changes, typically to the reaper/init) means the
// host that launched us is gone. Drive the same Shutdown() the host would
// have, so we don't linger as an orphan.
if (getppid() != host_pid_) {
lock.unlock();
std::cerr << "go-plugin: host process exited, shutting down" << std::endl;
Shutdown();
return;
}
watchdog_cv_.wait_for(lock, std::chrono::seconds(1));
}
});
}

void PluginServer::StopParentWatchdog() {
{
std::lock_guard<std::mutex> lock(watchdog_mu_);
watchdog_stop_ = true;
}
watchdog_cv_.notify_all();
// The watchdog reaches here via its own Shutdown() call on the host-death
// path; it must never join itself (that deadlocks). The destructor joins it.
if (parent_watchdog_.joinable() &&
parent_watchdog_.get_id() != std::this_thread::get_id())
parent_watchdog_.join();
}

// ── Serve convenience function ────────────────────────────────────────────────

ServeResult Serve(const ServeConfig &config) {
Expand Down
2 changes: 1 addition & 1 deletion vcpkg.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "go-plugin-cpp",
"version": "0.1.0",
"version": "0.1.1",
"dependencies": [
"grpc",
"openssl",
Expand Down
Loading