concord/internal/circuitbreaker/breaker.go
2025-10-30 01:31:11 +03:00

80 lines
1.2 KiB
Go

package circuitbreaker
import (
"errors"
"sync"
"time"
)
var ErrCircuitOpen = errors.New("circuit breaker is open")
type State int
const (
StateClosed State = iota
StateOpen
StateHalfOpen
)
type CircuitBreaker struct {
maxFailures int
timeout time.Duration
state State
failures int
lastFailure time.Time
mu sync.RWMutex
}
func New(maxFailures int, timeout time.Duration) *CircuitBreaker {
return &CircuitBreaker{
maxFailures: maxFailures,
timeout: timeout,
state: StateClosed,
}
}
func (cb *CircuitBreaker) Call(fn func() error) error {
cb.mu.Lock()
defer cb.mu.Unlock()
if cb.state == StateOpen {
if time.Since(cb.lastFailure) > cb.timeout {
cb.state = StateHalfOpen
} else {
return ErrCircuitOpen
}
}
err := fn()
if err != nil {
cb.failures++
cb.lastFailure = time.Now()
if cb.failures >= cb.maxFailures {
cb.state = StateOpen
}
return err
}
if cb.state == StateHalfOpen {
cb.state = StateClosed
}
cb.failures = 0
return nil
}
func (cb *CircuitBreaker) GetState() State {
cb.mu.RLock()
defer cb.mu.RUnlock()
return cb.state
}
func (cb *CircuitBreaker) Reset() {
cb.mu.Lock()
defer cb.mu.Unlock()
cb.state = StateClosed
cb.failures = 0
}