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() } }