From 81de8419034266432ac30fed854e5f804cc8d03e Mon Sep 17 00:00:00 2001 From: Snider Date: Mon, 9 Mar 2026 14:46:39 +0000 Subject: [PATCH] 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 --- daemon.go | 25 +++++++++++++++++++++++++ daemon_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/daemon.go b/daemon.go index 30cbf29..ffa71bf 100644 --- a/daemon.go +++ b/daemon.go @@ -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 { diff --git a/daemon_test.go b/daemon_test.go index 0fe52c7..6d958ff 100644 --- a/daemon_test.go +++ b/daemon_test.go @@ -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) +}