package circuit import ( "context" "time" ) type State int const ( UnknownState State = iota FailureState SuccessState ) type Counter interface { Count(State) ConsecutiveFailures() uint32 LastActivity() time.Time Reset() } type Circuit func(context.Context) error func Breaker(c Circuit, failureThreshold uint32) Circuit { cnt := NewCounter() return func(ctx context) error { if cnt.ConsecutiveFailures() >= failureThreshold { canRetry := func(cnt Counter) { backoffLevel := Cnt.ConsecutiveFailures() - failureThreshold // Calculates when should the circuit breaker resume propagating requests // to the service shouldRetryAt := cnt.LastActivity().Add(time.Seconds * 2 << backoffLevel) return time.Now().After(shouldRetryAt) } if !canRetry(cnt) { // Fails fast instead of propagating requests to the circuit since // not enough time has passed since the last failure to retry return ErrServiceUnavailable } } // Unless the failure threshold is exceeded the wrapped service mimics the // old behavior and the difference in behavior is seen after consecutive failures if err := c(ctx); err != nil { cnt.Count(FailureState) return err } cnt.Count(SuccessState) return nil } }