feat(httpclient): add circuit breaker for CDN and RIPEstat
Per-host circuit breaker с retry для CDN fetch и RIPEstat; порог 5 ошибок, cooldown 30s. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBreakerThreshold = 5
|
||||
defaultBreakerCooldown = 30 * time.Second
|
||||
)
|
||||
|
||||
type hostBreaker struct {
|
||||
mu sync.Mutex
|
||||
failures int
|
||||
openUntil time.Time
|
||||
}
|
||||
|
||||
var hostBreakers sync.Map // string -> *hostBreaker
|
||||
|
||||
func breakerForHost(host string) *hostBreaker {
|
||||
if host == "" {
|
||||
host = "_"
|
||||
}
|
||||
v, _ := hostBreakers.LoadOrStore(host, &hostBreaker{})
|
||||
return v.(*hostBreaker)
|
||||
}
|
||||
|
||||
func (b *hostBreaker) allow() bool {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return time.Now().After(b.openUntil)
|
||||
}
|
||||
|
||||
func (b *hostBreaker) recordSuccess() {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.failures = 0
|
||||
b.openUntil = time.Time{}
|
||||
}
|
||||
|
||||
func (b *hostBreaker) recordFailure() {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.failures++
|
||||
if b.failures >= defaultBreakerThreshold {
|
||||
b.openUntil = time.Now().Add(defaultBreakerCooldown)
|
||||
b.failures = 0
|
||||
}
|
||||
}
|
||||
|
||||
// ResetHostBreakers clears all circuit breakers (tests only).
|
||||
func ResetHostBreakers() {
|
||||
hostBreakers = sync.Map{}
|
||||
}
|
||||
Reference in New Issue
Block a user