Fix singleton ResourceManager data race (unlocked read vs test reset)

GetResourceManager returned the process-global via an unprotected read
(singleton_resources.go:50), while resetResourceManagerForTesting and
ResetResourceManager write it under resourceManagerMutex. A live background
task (graceful-degradation health check started by one test) reading the
singleton while a shuffled test resets it was a data race (found via
-race -shuffle: Write at singleton_resources_test.go:941/942 vs Read at
autocleanup.go:554 / error_recovery.go:910).

Fix: hold resourceManagerMutex around the read, matching the sibling
GetGlobalTaskRegistry (autocleanup.go:414). Added a deterministic
concurrent regression test (GetResourceManager vs reset under -race).

Verified: 4x full -race -count=1 -shuffle=on ./... all exit 0, 0 races,
0 fails; source-order suite green; lint clean.
This commit is contained in:
2026-08-12 23:20:44 +01:00
parent 7d2531e1e4
commit 01ad33c879
2 changed files with 44 additions and 0 deletions
+3
View File
@@ -36,6 +36,9 @@ type ResourceManager struct {
// GetResourceManager returns the global singleton ResourceManager instance
func GetResourceManager() *ResourceManager {
resourceManagerMutex.Lock()
defer resourceManagerMutex.Unlock()
resourceManagerOnce.Do(func() {
globalResourceManager = &ResourceManager{
httpClients: make(map[string]*http.Client),