Skip to content

feat: self-shutdown when the host process exits#1

Merged
devgianlu merged 1 commit into
mainfrom
feat/parent-death-watchdog
Jul 21, 2026
Merged

feat: self-shutdown when the host process exits#1
devgianlu merged 1 commit into
mainfrom
feat/parent-death-watchdog

Conversation

@devgianlu

@devgianlu devgianlu commented Jul 21, 2026

Copy link
Copy Markdown
Member

Problem

When a go-plugin host dies without killing its plugins — e.g. volumio5-core is SIGKILLed — nothing signals the C++ plugin. grpc::Server::Wait() only returns on Shutdown(), so the plugin blocks forever and orphans itself; the next host start then spawns a second copy.

Go plugins don't have this problem: the hashicorp/go-plugin Go server has built-in orphan protection. The C++ SDK had none — observed on-device with airplay2/qobuz/tidal (all C++) surviving a core SIGKILL while the Go plugins exited cleanly.

Fix

Add a parent-death watchdog to PluginServer, matching the Go server's built-in behaviour:

  • Start() records the host pid and spawns a watchdog thread.
  • The thread polls getppid(); on reparenting (host gone) it calls Shutdown(), unblocking Wait().
  • Shutdown() / the destructor stop and join the watchdog (condition-variable–woken, so no added shutdown latency).

Because this simply unblocks Wait(), each plugin's existing post-Wait() teardown runs unchanged — no per-plugin code required.

Why getppid() polling

  • PR_SET_PDEATHSIG races with the Go host's fork thread (spurious early trigger).
  • stdin-EOF is unusable here: the host's stdin is /dev/null when backgrounded by start-stop-daemon, so it would false-trigger immediately.

Validation

grpc isn't available on the dev host and plugin builds run inside the SDK container, so the watchdog logic was compile+run-checked standalone against a stub grpc::Server: clean build, both paths correct (stable parent idles then joins promptly; host-gone triggers immediate self-shutdown), and the double-Shutdown() is safe.

Consumers (volumio5-plugin-airplay2/qobuzconnect/tidalconnect) will pick this up via an overlay-port REF bump after merge.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Plugin servers now automatically shut down when the Go host process exits.
    • Prevented Wait() from blocking indefinitely after unexpected host termination.
    • Improved shutdown coordination to ensure the server and background monitoring stop cleanly.
  • Chores
    • Updated the package version to 0.1.1.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 834316fc-57f3-437f-ab0a-0ed9955b173a

📥 Commits

Reviewing files that changed from the base of the PR and between b535687 and 0ee54ae.

📒 Files selected for processing (3)
  • include/go_plugin/server.hpp
  • src/server.cpp
  • vcpkg.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • vcpkg.json
  • include/go_plugin/server.hpp
  • src/server.cpp

📝 Walkthrough

Walkthrough

The change adds parent-process monitoring to PluginServer, coordinates watchdog shutdown with server lifecycle operations, updates Wait() documentation, and increments the vcpkg package version.

Changes

Parent watchdog lifecycle

Layer / File(s) Summary
Watchdog state and lifecycle contract
include/go_plugin/server.hpp
Adds watchdog declarations, synchronization state, host PID tracking, required headers, and updated Wait() documentation.
Watchdog execution and shutdown coordination
src/server.cpp
Starts parent monitoring from Start(), detects parent PID changes, coordinates shutdown, and joins the watchdog thread during cleanup.

Package release metadata

Layer / File(s) Summary
Package version update
vcpkg.json
Increments the package version from 0.1.0 to 0.1.1.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: plugins now self-shutdown when the host process exits.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/server.cpp`:
- Around line 178-186: Update PluginServer::Shutdown() to call
StopParentWatchdog() for external callers after signaling the watchdog, ensuring
the watchdog is stopped and joined before shutdown returns. Preserve the
self-call protection in StopParentWatchdog() so the watchdog thread never
attempts to join itself.
- Around line 165-168: Update the server startup flow in Start() to capture
getppid() before any startup or handshake work can reparent the process, then
retain that value for host monitoring. Move the StartParentWatchdog() call to
execute only after startup succeeds, rather than relying on the later host_pid_
assignment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d4857338-3290-411a-a1c3-755a097d722f

📥 Commits

Reviewing files that changed from the base of the PR and between 41fbd4e and b535687.

📒 Files selected for processing (3)
  • include/go_plugin/server.hpp
  • src/server.cpp
  • vcpkg.json

Comment thread src/server.cpp Outdated
Comment thread src/server.cpp
@devgianlu
devgianlu force-pushed the feat/parent-death-watchdog branch from b535687 to 0ee54ae Compare July 21, 2026 13:32
Add a parent-death watchdog to PluginServer: when the go-plugin host dies
without killing the plugin (e.g. the host is SIGKILLed), nothing signals the
plugin and Wait() would block forever, orphaning the process. Detect
reparenting (getppid changes) and drive Shutdown(), mirroring the Go
go-plugin server's built-in orphan protection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@devgianlu
devgianlu force-pushed the feat/parent-death-watchdog branch from 0ee54ae to 3639c2a Compare July 21, 2026 13:35
@devgianlu
devgianlu merged commit 0ad3c5b into main Jul 21, 2026
1 check passed
@devgianlu
devgianlu deleted the feat/parent-death-watchdog branch July 21, 2026 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant