Fix daemon orphaning: track parent PID and exit when parent dies

- Add ParentPID field to DaemonLockInfo struct
- Daemon monitors parent process every 10 seconds
- Gracefully exits when parent process dies
- Prevents accumulation of orphaned daemons from dead sessions
- Fixes race conditions from multiple daemons on same database

Closes bd-zpnq
This commit is contained in:
Steve Yegge
2025-11-07 18:57:43 -08:00
parent a6c9579645
commit 7fa0c93195
5 changed files with 105 additions and 5 deletions

View File

@@ -73,12 +73,34 @@ func runGlobalDaemon(log daemonLogger) {
log.log("Global daemon stopped")
}
// checkParentProcessAlive checks if the parent process is still running.
// Returns true if parent is alive, false if it died.
// Returns true if parent PID is 0 or 1 (not tracked, or adopted by init).
func checkParentProcessAlive(parentPID int) bool {
if parentPID == 0 {
// Parent PID not tracked (older lock files)
return true
}
if parentPID == 1 {
// Adopted by init - parent died
return false
}
// Check if parent process is running
return isProcessRunning(parentPID)
}
// runEventLoop runs the daemon event loop (polling mode)
func runEventLoop(ctx context.Context, cancel context.CancelFunc, ticker *time.Ticker, doSync func(), server *rpc.Server, serverErrChan chan error, log daemonLogger) {
func runEventLoop(ctx context.Context, cancel context.CancelFunc, ticker *time.Ticker, doSync func(), server *rpc.Server, serverErrChan chan error, parentPID int, log daemonLogger) {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, daemonSignals...)
defer signal.Stop(sigChan)
// Parent process check (every 10 seconds)
parentCheckTicker := time.NewTicker(10 * time.Second)
defer parentCheckTicker.Stop()
for {
select {
case <-ticker.C:
@@ -86,6 +108,16 @@ func runEventLoop(ctx context.Context, cancel context.CancelFunc, ticker *time.T
return
}
doSync()
case <-parentCheckTicker.C:
// Check if parent process is still alive
if !checkParentProcessAlive(parentPID) {
log.log("Parent process (PID %d) died, shutting down daemon", parentPID)
cancel()
if err := server.Stop(); err != nil {
log.log("Error stopping server: %v", err)
}
return
}
case sig := <-sigChan:
if isReloadSignal(sig) {
log.log("Received reload signal, ignoring (daemon continues running)")