package circuitbreaker import ( "errors" "sync" "time" ) var ( // ErrCircuitOpen is returned when the circuit breaker is open ErrCircuitOpen = errors.New("circuit breaker is open") ) // State represents the circuit breaker state type State int const ( StateClosed State = iota StateOpen StateHalfOpen ) // CircuitBreaker implements the circuit breaker pattern for external service calls type CircuitBreaker struct { mu sync.RWMutex state State failureCount int successCount int lastFailureTime time.Time lastSuccessTime time.Time maxFailures int timeout time.Duration halfOpenAttempts int } // New creates a new circuit breaker // maxFailures: number of failures before opening the circuit // timeout: how long to wait before attempting to close the circuit func New(maxFailures int, timeout time.Duration) *CircuitBreaker { return &CircuitBreaker{ state: StateClosed, maxFailures: maxFailures, timeout: timeout, halfOpenAttempts: 3, // Allow 3 requests in half-open state } } // Call executes the function if the circuit is closed func (cb *CircuitBreaker) Call(fn func() error) error { cb.mu.Lock() // Check if we should transition from open to half-open if cb.state == StateOpen && time.Since(cb.lastFailureTime) > cb.timeout { cb.state = StateHalfOpen cb.successCount = 0 } // Reject if circuit is open if cb.state == StateOpen { cb.mu.Unlock() return ErrCircuitOpen } // Allow limited attempts in half-open state if cb.state == StateHalfOpen && cb.successCount >= cb.halfOpenAttempts { cb.mu.Unlock() return ErrCircuitOpen } cb.mu.Unlock() // Execute the function err := fn() cb.mu.Lock() defer cb.mu.Unlock() if err != nil { cb.onFailure() return err } cb.onSuccess() return nil } // onFailure is called when a request fails func (cb *CircuitBreaker) onFailure() { cb.failureCount++ cb.lastFailureTime = time.Now() if cb.state == StateHalfOpen { // Immediately open if failure in half-open state cb.state = StateOpen cb.successCount = 0 return } if cb.failureCount >= cb.maxFailures { cb.state = StateOpen } } // onSuccess is called when a request succeeds func (cb *CircuitBreaker) onSuccess() { cb.lastSuccessTime = time.Now() if cb.state == StateHalfOpen { cb.successCount++ if cb.successCount >= cb.halfOpenAttempts { // Close the circuit after successful attempts cb.state = StateClosed cb.failureCount = 0 cb.successCount = 0 } return } if cb.state == StateClosed { // Reset failure count on success cb.failureCount = 0 } } // GetState returns the current state of the circuit breaker func (cb *CircuitBreaker) GetState() State { cb.mu.RLock() defer cb.mu.RUnlock() return cb.state } // Reset manually resets the circuit breaker to closed state func (cb *CircuitBreaker) Reset() { cb.mu.Lock() defer cb.mu.Unlock() cb.state = StateClosed cb.failureCount = 0 cb.successCount = 0 }