zoraxy-waf/circuit_breaker.go

76 lines
1.7 KiB
Go

package main
import (
"sync"
"time"
)
// CircuitState represents the breaker's current state.
type CircuitState int
const (
StateClosed CircuitState = iota // normal — requests pass through
StateOpen // tripped — fail-open
StateHalfOpen // testing recovery
)
// CircuitBreaker implements a simple fail-open pattern.
// When failures exceed the threshold within the window, the breaker opens.
// After a cooldown, it moves to half-open to test recovery.
type CircuitBreaker struct {
mu sync.Mutex
state CircuitState
failures int
lastFailure time.Time
maxFailures int
window time.Duration
cooldown time.Duration
openedAt time.Time
}
func newCircuitBreaker() *CircuitBreaker {
return &CircuitBreaker{
state: StateClosed,
maxFailures: 5,
window: 30 * time.Second,
cooldown: 10 * time.Second,
}
}
// state returns the current state, testing for recovery if half-open and cooldown elapsed.
func (cb *CircuitBreaker) State() CircuitState {
cb.mu.Lock()
defer cb.mu.Unlock()
if cb.state == StateOpen && time.Since(cb.openedAt) > cb.cooldown {
cb.state = StateHalfOpen
}
// Reset failure count if window elapsed.
if time.Since(cb.lastFailure) > cb.window {
cb.failures = 0
if cb.state == StateHalfOpen {
cb.state = StateClosed
}
}
return cb.state
}
func (cb *CircuitBreaker) RecordSuccess() {
cb.mu.Lock()
defer cb.mu.Unlock()
if cb.state == StateHalfOpen {
cb.state = StateClosed
}
cb.failures = 0
}
func (cb *CircuitBreaker) RecordFailure() {
cb.mu.Lock()
defer cb.mu.Unlock()
cb.failures++
cb.lastFailure = time.Now()
if cb.failures >= cb.maxFailures {
cb.state = StateOpen
cb.openedAt = time.Now()
}
}