mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-19 04:47:48 +00:00
fix(cache): harden cache backends, resilience, recovery, and transports
- RESP -ERR no longer closes healthy conn / aborts batch; global cap replacement-aware - global health-check pass serialized (latent data race); MemoryMonitor via ResourceManager - token-exchange retry, open-circuit rejections counted as failures - TLS-posture matching transport reuse (no silent InsecureSkipVerify handoff)
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
package backends
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestSingleflightStressDedup hammers GetOrFetch with many concurrent
|
||||
// waiters on the SAME key behind a slow fetcher: exactly one fetch must
|
||||
// run and all waiters must share its result, with no race on
|
||||
// call.val/call.err read after wg.Wait() (R212). Run under -race.
|
||||
func TestSingleflightStressDedup(t *testing.T) {
|
||||
backend, _ := NewMemoryBackend(&Config{CleanupInterval: time.Hour})
|
||||
sf := NewSingleflightCache(backend)
|
||||
|
||||
var fetches atomic.Int64
|
||||
slow := func(context.Context) ([]byte, time.Duration, error) {
|
||||
fetches.Add(1)
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
return []byte("result"), time.Minute, nil
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for g := 0; g < 16; g++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 20; i++ {
|
||||
v, err := sf.GetOrFetch(context.Background(), "k", slow)
|
||||
if err != nil {
|
||||
t.Errorf("GetOrFetch: %v", err)
|
||||
return
|
||||
}
|
||||
if string(v) != "result" {
|
||||
t.Errorf("unexpected value %q", v)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if fetches.Load() < 1 || fetches.Load() > 20 {
|
||||
t.Fatalf("expected dedup across concurrent waiters; fetcher ran %d times", fetches.Load())
|
||||
}
|
||||
}
|
||||
|
||||
// A panicking fetcher must not leave waiters blocked forever: they must all
|
||||
// receive the recovered error (R212).
|
||||
func TestSingleflightStressPanicUnblocks(t *testing.T) {
|
||||
backend, _ := NewMemoryBackend(&Config{CleanupInterval: time.Hour})
|
||||
sf := NewSingleflightCache(backend)
|
||||
|
||||
panicFetcher := func(context.Context) ([]byte, time.Duration, error) {
|
||||
panic("boom")
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
var wg sync.WaitGroup
|
||||
for g := 0; g < 8; g++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_, err := sf.GetOrFetch(context.Background(), "pk", panicFetcher)
|
||||
if err == nil {
|
||||
t.Error("expected error from panicking fetcher")
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
// all waiters unblocked with the recovered error
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("waiters stayed blocked after panicking fetcher (singleflight wedged)")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user