fix: Manager.Stop() is now idempotent

Previously a second Stop() call would panic from a double-close on
eventBus and re-stop the healthChecker/watchdog whose contexts had
already been cancelled. Wrapping the body in sync.Once makes
sequential and concurrent double-Stop safe.

TestManager_Stop_Idempotent covers both paths.
This commit is contained in:
2026-05-06 11:02:41 +01:00
parent 0a8c872b01
commit 0ccc855123
2 changed files with 80 additions and 36 deletions
+41
View File
@@ -165,3 +165,44 @@ func TestManager_CurrentConfig_RaceFree(t *testing.T) {
close(stop)
wg.Wait()
}
// TestManager_Stop_Idempotent verifies that calling Manager.Stop() multiple
// times — sequentially or concurrently — does not panic from a double-close
// of eventBus or a double Stop on healthChecker/watchdog. The body of Stop()
// is wrapped in sync.Once.
func TestManager_Stop_Idempotent(t *testing.T) {
manager, err := NewManager(false)
if err != nil {
t.Skip("Skipping test - no kubeconfig available")
}
if err := manager.Start(&config.Config{}); err != nil {
t.Fatalf("Start failed: %v", err)
}
// Sequential double-stop must not panic.
manager.Stop()
manager.Stop()
// Build a second manager and call Stop concurrently from many goroutines —
// any non-idempotent close path would panic at least one of them.
m2, err := NewManager(false)
if err != nil {
t.Skip("Skipping test - no kubeconfig available")
}
if err := m2.Start(&config.Config{}); err != nil {
t.Fatalf("Start failed: %v", err)
}
const callers = 16
var wg sync.WaitGroup
wg.Add(callers)
start := make(chan struct{})
for i := 0; i < callers; i++ {
go func() {
defer wg.Done()
<-start
m2.Stop()
}()
}
close(start)
wg.Wait()
}