fix(tests): configurable retry delays and circuit backoff overflow

Three bugs fixed:

1. **Test suite timeout** — The main-package test suite timed out at 120s
   because each failing request triggered 7 retries with exponential
   backoff (500ms base → 10s max ≈ 25.5s per request). Tests that trip
   the circuit breaker (3 requests × 25.5s = 76.5s) plus
   TestStartHTTPProxy_ErrorHandlerConfigured (another 25.5s) pushed
   cumulative time past the limit.

   Fix: add `Client.RetryBaseDelayMs` / `Client.RetryMaxDelayMs` config
   fields (env: CLIENT_RETRY_BASE_DELAY_MS / CLIENT_RETRY_MAX_DELAY_MS,
   defaults 500ms / 10000ms). Tests override these to 10ms so retry
   exhaustion completes in milliseconds. Full suite now runs in ~35s.

2. **effectiveCircuitBackoff float overflow** — On amd64, converting
   `+Inf` (float64) to `time.Duration` (int64) saturates to MinInt64,
   not MaxInt64. The existing "< base" guard then returned `base` instead
   of `maxSaneCircuitBackoff`, breaking the sanity ceiling for an uncapped
   config with a high trip count. Fix: detect `math.IsInf / math.IsNaN`
   before the cast and return the ceiling directly.

3. **TestStartHTTPProxy_ErrorHandlerConfigured** — standalone top-level
   test calling parseConfig() directly, bypassing the TestSuite override.
   Now sets and restores RetryBaseDelayMs/RetryMaxDelayMs explicitly.

Co-authored-by: lukaszraczylo <[email protected]>
This commit is contained in:
copilot-swe-agent[bot]
2026-08-14 15:58:10 +00:00
committed by GitHub
co-authored by lukaszraczylo
parent aaa08dfbb2
commit 8d1518b961
5 changed files with 72 additions and 18 deletions
+28 -5
View File
@@ -477,7 +477,16 @@ func effectiveCircuitBackoff(base time.Duration, multiplier float64, maxBackoff
}
exponent := float64(trips - 1)
effective := time.Duration(float64(base) * math.Pow(multiplier, exponent))
raw := float64(base) * math.Pow(multiplier, exponent)
// On amd64 the float64->int64 conversion for +Inf saturates to MinInt64
// rather than MaxInt64, so the subsequent "< base" guard would return base
// instead of the hard ceiling. Detect overflow before the cast.
if math.IsInf(raw, 1) || math.IsNaN(raw) {
return maxSaneCircuitBackoff
}
effective := time.Duration(raw)
if effective < base {
// Overflow or float rounding pushed us below base; never shorten.
return base
@@ -937,17 +946,31 @@ func performProxyRequestWithEnhancedRetries(c fiber.Ctx, proxyURL string, backen
var initialDelay time.Duration
var maxDelayTime time.Duration
// Read per-request base and max delay from config; fall back to coded
// defaults when zero (e.g. during very early startup before parseConfig).
baseDelayMs := cfg.Client.RetryBaseDelayMs
if baseDelayMs <= 0 {
baseDelayMs = 500
}
maxDelayMs := cfg.Client.RetryMaxDelayMs
if maxDelayMs <= 0 {
maxDelayMs = 10000
}
if backendUnhealthy {
// Backend is known to be unhealthy, fail fast
// Circuit breaker should handle this, so reduce retries
attempts = 3
initialDelay = 500 * time.Millisecond
maxDelayTime = 5 * time.Second
initialDelay = time.Duration(baseDelayMs) * time.Millisecond
maxDelayTime = time.Duration(maxDelayMs/2) * time.Millisecond
if maxDelayTime < initialDelay {
maxDelayTime = initialDelay
}
} else {
// Normal retry strategy
attempts = 7
initialDelay = 500 * time.Millisecond
maxDelayTime = 10 * time.Second
initialDelay = time.Duration(baseDelayMs) * time.Millisecond
maxDelayTime = time.Duration(maxDelayMs) * time.Millisecond
}
return retry.Do(