From 6db0ad26e3ff08c3ad828fb19643b50146172e09 Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 14 Apr 2026 19:44:06 +0100 Subject: [PATCH] fix(registry): idempotent Unregister/Release when file missing The coreio.Local.Delete error wraps the underlying os.ErrNotExist through core.E, so the prior os.IsNotExist check on the registry Unregister path never matched. Same wrapping broke the daemon Stop path that relied on pidfile.Release being a no-op for absent files. Switch both to coreio.Local.Exists before Delete, which is the idempotent pattern the callers already assume. Adds coverage for TestPIDFile_Release_MissingIsNoop and fixes TestRegistry_Unregister MissingIsNoop. Co-Authored-By: Virgil --- pidfile.go | 4 ++++ pidfile_test.go | 6 ++++++ registry.go | 9 +++++---- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/pidfile.go b/pidfile.go index bade76f..df9b965 100644 --- a/pidfile.go +++ b/pidfile.go @@ -67,6 +67,7 @@ func (p *PIDFile) Acquire() error { } // Release removes the PID file. +// Returns nil if the PID file does not exist. // // Example: // @@ -74,6 +75,9 @@ func (p *PIDFile) Acquire() error { func (p *PIDFile) Release() error { p.mu.Lock() defer p.mu.Unlock() + if !coreio.Local.Exists(p.path) { + return nil + } if err := coreio.Local.Delete(p.path); err != nil { return core.E("pidfile.release", "failed to remove PID file", err) } diff --git a/pidfile_test.go b/pidfile_test.go index abdfa29..a35c9cb 100644 --- a/pidfile_test.go +++ b/pidfile_test.go @@ -47,6 +47,12 @@ func TestPIDFile_Path_Good(t *testing.T) { assert.Equal(t, "/tmp/test.pid", pid.Path()) } +func TestPIDFile_Release_MissingIsNoop(t *testing.T) { + pidPath := core.JoinPath(t.TempDir(), "absent.pid") + pid := NewPIDFile(pidPath) + require.NoError(t, pid.Release()) +} + func TestReadPID_Missing_Bad(t *testing.T) { pid, running := ReadPID("/nonexistent/path.pid") assert.Equal(t, 0, pid) diff --git a/registry.go b/registry.go index 0c091c4..76d0247 100644 --- a/registry.go +++ b/registry.go @@ -88,10 +88,11 @@ func (r *Registry) Register(entry DaemonEntry) error { // // _ = reg.Unregister("app", "serve") func (r *Registry) Unregister(code, daemon string) error { - if err := coreio.Local.Delete(r.entryPath(code, daemon)); err != nil { - if os.IsNotExist(err) { - return nil - } + path := r.entryPath(code, daemon) + if !coreio.Local.Exists(path) { + return nil + } + if err := coreio.Local.Delete(path); err != nil { return coreerr.E("Registry.Unregister", "failed to delete entry file", err) } return nil