refactor(18-graceful-shutdown): harden mock shutdown lifecycle in internal/mock

This commit is contained in:
rcourtman
2026-02-12 00:22:31 +00:00
parent 0b6ecd77ef
commit 62d0afa623
3 changed files with 62 additions and 3 deletions

View File

@@ -14,6 +14,7 @@ import (
var (
dataMu sync.RWMutex
setEnabledMu sync.Mutex
mockData models.StateSnapshot
mockAlerts []models.Alert
mockConfig = DefaultConfig
@@ -44,6 +45,9 @@ func SetEnabled(enable bool) {
}
func setEnabled(enable bool, fromInit bool) {
setEnabledMu.Lock()
defer setEnabledMu.Unlock()
current := enabled.Load()
if current == enable {
// Still update env so other processes see the latest value when not invoked from init.
@@ -111,7 +115,12 @@ func disableMockMode() {
return
}
enabled.Store(false)
stopUpdateLoopLocked()
stopUpdateLoopSignalLocked()
dataMu.Unlock()
waitForUpdateLoopStop()
dataMu.Lock()
mockData = models.StateSnapshot{}
mockAlerts = nil
dataMu.Unlock()
@@ -142,6 +151,11 @@ func startUpdateLoopLocked() {
}
func stopUpdateLoopLocked() {
stopUpdateLoopSignalLocked()
waitForUpdateLoopStop()
}
func stopUpdateLoopSignalLocked() {
if ch := stopUpdatesCh; ch != nil {
close(ch)
stopUpdatesCh = nil
@@ -150,7 +164,9 @@ func stopUpdateLoopLocked() {
ticker.Stop()
updateTicker = nil
}
// Wait for the update goroutine to exit
}
func waitForUpdateLoopStop() {
updateLoopWg.Wait()
}

View File

@@ -0,0 +1,38 @@
package mock
import (
"testing"
"time"
)
func TestSetEnabledDisableDoesNotDeadlockWhenLoopNeedsStateLock(t *testing.T) {
SetEnabled(false)
t.Cleanup(func() {
SetEnabled(false)
})
dataMu.Lock()
enabled.Store(true)
stopUpdatesCh = make(chan struct{})
updateTicker = time.NewTicker(time.Hour)
updateLoopWg.Add(1)
go func(stop <-chan struct{}) {
defer updateLoopWg.Done()
<-stop
dataMu.Lock()
dataMu.Unlock()
}(stopUpdatesCh)
dataMu.Unlock()
done := make(chan struct{})
go func() {
SetEnabled(false)
close(done)
}()
select {
case <-done:
case <-time.After(2 * time.Second):
t.Fatal("SetEnabled(false) timed out waiting for update loop shutdown")
}
}

View File

@@ -23,7 +23,7 @@ import (
"github.com/rcourtman/pulse-go-rewrite/internal/license"
"github.com/rcourtman/pulse-go-rewrite/internal/license/conversion"
"github.com/rcourtman/pulse-go-rewrite/internal/logging"
_ "github.com/rcourtman/pulse-go-rewrite/internal/mock" // Import for init() to run
"github.com/rcourtman/pulse-go-rewrite/internal/mock"
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
"github.com/rcourtman/pulse-go-rewrite/internal/websocket"
"github.com/rcourtman/pulse-go-rewrite/pkg/audit"
@@ -443,6 +443,11 @@ shutdown:
// Stop AI chat service (kills sidecar process group)
router.StopAIChat(shutdownCtx)
// Ensure mock-mode background update ticker is stopped before process exit.
if mock.IsMockEnabled() {
mock.SetEnabled(false)
}
cancel()
reloadableMonitor.Stop()