mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-11-03 07:44:13 +00:00
The window and cooldown resets set attempts to 1. The gate then counted the same call again, so a reset window allowed one call fewer than a fresh one. With a limit of 1 the session locked out at once. Resets now start at 0. The half-open breaker admitted every caller. It now admits one probe, claimed with a compare-and-swap. A stale probe is replaced after OpenDuration, so a lost probe cannot wedge the breaker.
94 lines
2.3 KiB
Go
94 lines
2.3 KiB
Go
package traefikoidc
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// After a window reset the tracker must allow the full MaxRefreshAttempts,
|
|
// the same as a fresh tracker.
|
|
func TestRefreshWindowResetAllowsFullQuota(t *testing.T) {
|
|
cfg := DefaultRefreshCoordinatorConfig()
|
|
cfg.MaxRefreshAttempts = 3
|
|
cfg.RefreshAttemptWindow = 50 * time.Millisecond
|
|
cfg.RefreshCooldownPeriod = time.Hour
|
|
rc := NewRefreshCoordinator(cfg, GetSingletonNoOpLogger())
|
|
defer rc.Shutdown()
|
|
|
|
pass := func() int {
|
|
n := 0
|
|
for i := 0; i < 10; i++ {
|
|
if rc.isInCooldown("s") {
|
|
break
|
|
}
|
|
rc.recordRefreshAttempt("s")
|
|
n++
|
|
}
|
|
return n
|
|
}
|
|
if got := pass(); got != 3 {
|
|
t.Fatalf("fresh window passes=%d want 3", got)
|
|
}
|
|
// Window expiry alone cannot reset while in cooldown, so use a new
|
|
// session that never hits the cap: record 1, wait out the window.
|
|
rc.recordRefreshAttempt("w")
|
|
time.Sleep(80 * time.Millisecond)
|
|
n := 0
|
|
for i := 0; i < 10; i++ {
|
|
if rc.isInCooldown("w") {
|
|
break
|
|
}
|
|
rc.recordRefreshAttempt("w")
|
|
n++
|
|
}
|
|
if n != 3 {
|
|
t.Fatalf("post-reset window passes=%d want 3", n)
|
|
}
|
|
}
|
|
|
|
// Half-open admits a single probe until it reports.
|
|
func TestRefreshBreakerHalfOpenSingleProbe(t *testing.T) {
|
|
cb := &RefreshCircuitBreaker{config: RefreshCircuitBreakerConfig{
|
|
MaxFailures: 1, OpenDuration: 20 * time.Millisecond, HalfOpenRequests: 1,
|
|
}}
|
|
cb.RecordFailure()
|
|
time.Sleep(40 * time.Millisecond)
|
|
var admitted int32
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 20; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if cb.AllowRequest() {
|
|
atomic.AddInt32(&admitted, 1)
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
if admitted != 1 {
|
|
t.Fatalf("half-open admitted %d, want 1", admitted)
|
|
}
|
|
cb.RecordSuccess()
|
|
if !cb.AllowRequest() {
|
|
t.Fatal("breaker must close after probe success")
|
|
}
|
|
}
|
|
|
|
// A probe that never reports must not wedge the breaker.
|
|
func TestRefreshBreakerHalfOpenProbeTimeout(t *testing.T) {
|
|
cb := &RefreshCircuitBreaker{config: RefreshCircuitBreakerConfig{
|
|
MaxFailures: 1, OpenDuration: 20 * time.Millisecond, HalfOpenRequests: 1,
|
|
}}
|
|
cb.RecordFailure()
|
|
time.Sleep(40 * time.Millisecond)
|
|
if !cb.AllowRequest() || cb.AllowRequest() {
|
|
t.Fatal("want exactly one probe")
|
|
}
|
|
time.Sleep(40 * time.Millisecond)
|
|
if !cb.AllowRequest() {
|
|
t.Fatal("stale probe must allow a new one")
|
|
}
|
|
}
|