feat: auto-register/unregister daemons via optional Registry

When Registry is set on DaemonOptions, Start() auto-registers the daemon
(filling PID and Health address) and Stop() auto-unregisters it. Consumers
without a registry are completely unaffected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-03-09 14:46:39 +00:00
parent 2a26948d44
commit 81de841903
2 changed files with 56 additions and 0 deletions

View file

@ -26,6 +26,13 @@ type DaemonOptions struct {
// HealthChecks are additional health check functions.
HealthChecks []HealthCheck
// Registry for tracking this daemon. Leave nil to skip registration.
Registry *Registry
// RegistryEntry provides the code and daemon name for registration.
// PID, Health, and Started are filled automatically.
RegistryEntry DaemonEntry
}
// Daemon manages daemon lifecycle: PID file, health server, graceful shutdown.
@ -84,6 +91,19 @@ func (d *Daemon) Start() error {
}
d.running = true
// Auto-register if registry is set
if d.opts.Registry != nil {
entry := d.opts.RegistryEntry
entry.PID = os.Getpid()
if d.health != nil {
entry.Health = d.health.Addr()
}
if err := d.opts.Registry.Register(entry); err != nil {
return fmt.Errorf("registry: %w", err)
}
}
return nil
}
@ -128,6 +148,11 @@ func (d *Daemon) Stop() error {
}
}
// Auto-unregister
if d.opts.Registry != nil {
_ = d.opts.Registry.Unregister(d.opts.RegistryEntry.Code, d.opts.RegistryEntry.Daemon)
}
d.running = false
if len(errs) > 0 {

View file

@ -3,6 +3,7 @@ package process
import (
"context"
"net/http"
"os"
"path/filepath"
"testing"
"time"
@ -91,3 +92,33 @@ func TestDaemon_DefaultShutdownTimeout(t *testing.T) {
d := NewDaemon(DaemonOptions{})
assert.Equal(t, 30*time.Second, d.opts.ShutdownTimeout)
}
func TestDaemon_AutoRegisters(t *testing.T) {
dir := t.TempDir()
reg := NewRegistry(filepath.Join(dir, "daemons"))
d := NewDaemon(DaemonOptions{
HealthAddr: "127.0.0.1:0",
Registry: reg,
RegistryEntry: DaemonEntry{
Code: "test-app",
Daemon: "serve",
},
})
err := d.Start()
require.NoError(t, err)
// Should be registered
entry, ok := reg.Get("test-app", "serve")
require.True(t, ok)
assert.Equal(t, os.Getpid(), entry.PID)
assert.NotEmpty(t, entry.Health)
// Stop should unregister
err = d.Stop()
require.NoError(t, err)
_, ok = reg.Get("test-app", "serve")
assert.False(t, ok)
}