mirror of
https://github.com/lukaszraczylo/kportal.git
synced 2026-07-15 07:57:49 +00:00
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:
@@ -165,3 +165,44 @@ func TestManager_CurrentConfig_RaceFree(t *testing.T) {
|
|||||||
close(stop)
|
close(stop)
|
||||||
wg.Wait()
|
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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ type Manager struct {
|
|||||||
// (registered in startWorker) and written by Start/Reload.
|
// (registered in startWorker) and written by Start/Reload.
|
||||||
currentConfig *config.Config
|
currentConfig *config.Config
|
||||||
workersMu sync.RWMutex
|
workersMu sync.RWMutex
|
||||||
|
stopOnce sync.Once
|
||||||
verbose bool
|
verbose bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,6 +224,7 @@ func (m *Manager) Start(cfg *config.Config) error {
|
|||||||
|
|
||||||
// Stop gracefully stops all port-forward workers.
|
// Stop gracefully stops all port-forward workers.
|
||||||
func (m *Manager) Stop() {
|
func (m *Manager) Stop() {
|
||||||
|
m.stopOnce.Do(func() {
|
||||||
log.Printf("Stopping all port-forwards...")
|
log.Printf("Stopping all port-forwards...")
|
||||||
|
|
||||||
// Stop health checker and watchdog first
|
// Stop health checker and watchdog first
|
||||||
@@ -269,6 +271,7 @@ func (m *Manager) Stop() {
|
|||||||
m.workersMu.Unlock()
|
m.workersMu.Unlock()
|
||||||
|
|
||||||
log.Printf("All port-forwards stopped")
|
log.Printf("All port-forwards stopped")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload applies a new configuration with hot-reload logic.
|
// Reload applies a new configuration with hot-reload logic.
|
||||||
|
|||||||
Reference in New Issue
Block a user