mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-17 04:37:48 +00:00
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:
@@ -36,6 +36,9 @@ type ResourceManager struct {
|
|||||||
|
|
||||||
// GetResourceManager returns the global singleton ResourceManager instance
|
// GetResourceManager returns the global singleton ResourceManager instance
|
||||||
func GetResourceManager() *ResourceManager {
|
func GetResourceManager() *ResourceManager {
|
||||||
|
resourceManagerMutex.Lock()
|
||||||
|
defer resourceManagerMutex.Unlock()
|
||||||
|
|
||||||
resourceManagerOnce.Do(func() {
|
resourceManagerOnce.Do(func() {
|
||||||
globalResourceManager = &ResourceManager{
|
globalResourceManager = &ResourceManager{
|
||||||
httpClients: make(map[string]*http.Client),
|
httpClients: make(map[string]*http.Client),
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package traefikoidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestConcurrentGetResourceManagerVsReset stresses GetResourceManager against
|
||||||
|
// resetResourceManagerForTesting under -race. GetResourceManager returns the
|
||||||
|
// process-global via an unprotected read unless the caller holds
|
||||||
|
// resourceManagerMutex (regression: a live background task reading the
|
||||||
|
// singleton while a shuffled test resets it was a data race). Run with -race;
|
||||||
|
// this fails (race) before the mutex fix and passes after.
|
||||||
|
func TestConcurrentGetResourceManagerVsReset(t *testing.T) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Readers constantly re-fetch the global, as a background task would.
|
||||||
|
for g := 0; g < 8; g++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for i := 0; i < 2000; i++ {
|
||||||
|
if rm := GetResourceManager(); rm == nil {
|
||||||
|
t.Log("GetResourceManager returned nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resetter repeatedly pins/unpins the singleton with the test helper,
|
||||||
|
// mirroring resetResourceManagerForTesting's writes in a shuffled suite.
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for i := 0; i < 200; i++ {
|
||||||
|
resetResourceManagerForTesting()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user