github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/f/breaker.go (about)

     1  package f
     2  
     3  // Implements the Circuit Breaker pattern.
     4  // See https://msdn.microsoft.com/en-us/library/dn589784.aspx.
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"sync"
    10  	"time"
    11  )
    12  
    13  // BreakerState is a type that represents a state of CircuitBreaker.
    14  type BreakerState int
    15  
    16  // These constants are states of CircuitBreaker.
    17  const (
    18  	BreakerStateClosed BreakerState = iota
    19  	BreakerStateHalfOpen
    20  	BreakerStateOpen
    21  )
    22  
    23  var (
    24  	// ErrTooManyRequests is returned when the CB state is half open and the requests count is over the cb maxRequests
    25  	ErrTooManyRequests = errors.New("too many requests")
    26  	// ErrBreakerOpenState is returned when the CB state is open
    27  	ErrBreakerOpenState = errors.New("circuit breaker is open")
    28  )
    29  
    30  // String implements stringer interface.
    31  func (s BreakerState) String() string {
    32  	switch s {
    33  	case BreakerStateClosed:
    34  		return "closed"
    35  	case BreakerStateHalfOpen:
    36  		return "half-open"
    37  	case BreakerStateOpen:
    38  		return "open"
    39  	default:
    40  		return fmt.Sprintf("unknown state: %d", s)
    41  	}
    42  }
    43  
    44  // BreakerCounts holds the numbers of requests and their successes/failures.
    45  // CircuitBreaker clears the internal BreakerCounts either
    46  // on the change of the state or at the closed-state intervals.
    47  // BreakerCounts ignores the results of the requests sent before clearing.
    48  type BreakerCounts struct {
    49  	Requests             uint32
    50  	TotalSuccesses       uint32
    51  	TotalFailures        uint32
    52  	ConsecutiveSuccesses uint32
    53  	ConsecutiveFailures  uint32
    54  }
    55  
    56  func (c *BreakerCounts) onRequest() {
    57  	c.Requests++
    58  }
    59  
    60  func (c *BreakerCounts) onSuccess() {
    61  	c.TotalSuccesses++
    62  	c.ConsecutiveSuccesses++
    63  	c.ConsecutiveFailures = 0
    64  }
    65  
    66  func (c *BreakerCounts) onFailure() {
    67  	c.TotalFailures++
    68  	c.ConsecutiveFailures++
    69  	c.ConsecutiveSuccesses = 0
    70  }
    71  
    72  func (c *BreakerCounts) clear() {
    73  	c.Requests = 0
    74  	c.TotalSuccesses = 0
    75  	c.TotalFailures = 0
    76  	c.ConsecutiveSuccesses = 0
    77  	c.ConsecutiveFailures = 0
    78  }
    79  
    80  // BreakerSettings configures CircuitBreaker:
    81  //
    82  // Name is the name of the CircuitBreaker.
    83  //
    84  // MaxRequests is the maximum number of requests allowed to pass through
    85  // when the CircuitBreaker is half-open.
    86  // If MaxRequests is 0, the CircuitBreaker allows only 1 request.
    87  //
    88  // Interval is the cyclic period of the closed state
    89  // for the CircuitBreaker to clear the internal BreakerCounts.
    90  // If Interval is less than or equal to 0, the CircuitBreaker doesn't clear internal BreakerCounts during the closed state.
    91  //
    92  // Timeout is the period of the open state,
    93  // after which the state of the CircuitBreaker becomes half-open.
    94  // If Timeout is less than or equal to 0, the timeout value of the CircuitBreaker is set to 60 seconds.
    95  //
    96  // ReadyToTrip is called with a copy of BreakerCounts whenever a request fails in the closed state.
    97  // If ReadyToTrip returns true, the CircuitBreaker will be placed into the open state.
    98  // If ReadyToTrip is nil, default ReadyToTrip is used.
    99  // Default ReadyToTrip returns true when the number of consecutive failures is more than 5.
   100  //
   101  // OnStateChange is called whenever the state of the CircuitBreaker changes.
   102  //
   103  // IsSuccessful is called with the error returned from the request, if not nil.
   104  // If IsSuccessful returns false, the error is considered a failure, and is counted towards tripping the circuit breaker.
   105  // If IsSuccessful returns true, the error will be returned to the caller without tripping the circuit breaker.
   106  // If IsSuccessful is nil, default IsSuccessful is used, which returns false for all non-nil errors.
   107  type BreakerSettings struct {
   108  	Name          string
   109  	MaxRequests   uint32
   110  	Interval      time.Duration
   111  	Timeout       time.Duration
   112  	ReadyToTrip   func(counts BreakerCounts) bool
   113  	OnStateChange func(name string, from BreakerState, to BreakerState)
   114  	IsSuccessful  func(err error) bool
   115  }
   116  
   117  // CircuitBreaker is a state machine to prevent sending requests that are likely to fail.
   118  type CircuitBreaker struct {
   119  	name          string
   120  	maxRequests   uint32
   121  	interval      time.Duration
   122  	timeout       time.Duration
   123  	readyToTrip   func(counts BreakerCounts) bool
   124  	isSuccessful  func(err error) bool
   125  	onStateChange func(name string, from BreakerState, to BreakerState)
   126  
   127  	mutex      sync.Mutex
   128  	state      BreakerState
   129  	generation uint64
   130  	counts     BreakerCounts
   131  	expiry     time.Time
   132  }
   133  
   134  // TwoStepCircuitBreaker is like CircuitBreaker but instead of surrounding a function
   135  // with the breaker functionality, it only checks whether a request can proceed and
   136  // expects the caller to report the outcome in a separate step using a callback.
   137  type TwoStepCircuitBreaker struct {
   138  	cb *CircuitBreaker
   139  }
   140  
   141  // NewCircuitBreaker returns a new CircuitBreaker configured with the given BreakerSettings.
   142  func NewCircuitBreaker(st BreakerSettings) *CircuitBreaker {
   143  	cb := new(CircuitBreaker)
   144  
   145  	cb.name = st.Name
   146  	cb.onStateChange = st.OnStateChange
   147  
   148  	if st.MaxRequests == 0 {
   149  		cb.maxRequests = 1
   150  	} else {
   151  		cb.maxRequests = st.MaxRequests
   152  	}
   153  
   154  	if st.Interval <= 0 {
   155  		cb.interval = defaultBreakerInterval
   156  	} else {
   157  		cb.interval = st.Interval
   158  	}
   159  
   160  	if st.Timeout <= 0 {
   161  		cb.timeout = defaultBreakerTimeout
   162  	} else {
   163  		cb.timeout = st.Timeout
   164  	}
   165  
   166  	if st.ReadyToTrip == nil {
   167  		cb.readyToTrip = defaultReadyToTrip
   168  	} else {
   169  		cb.readyToTrip = st.ReadyToTrip
   170  	}
   171  
   172  	if st.IsSuccessful == nil {
   173  		cb.isSuccessful = defaultIsSuccessful
   174  	} else {
   175  		cb.isSuccessful = st.IsSuccessful
   176  	}
   177  
   178  	cb.toNewGeneration(time.Now())
   179  
   180  	return cb
   181  }
   182  
   183  // NewTwoStepCircuitBreaker returns a new TwoStepCircuitBreaker configured with the given BreakerSettings.
   184  func NewTwoStepCircuitBreaker(st BreakerSettings) *TwoStepCircuitBreaker {
   185  	return &TwoStepCircuitBreaker{
   186  		cb: NewCircuitBreaker(st),
   187  	}
   188  }
   189  
   190  const defaultBreakerInterval = time.Duration(0) * time.Second
   191  const defaultBreakerTimeout = time.Duration(60) * time.Second
   192  
   193  func defaultReadyToTrip(counts BreakerCounts) bool {
   194  	return counts.ConsecutiveFailures > 5
   195  }
   196  
   197  func defaultIsSuccessful(err error) bool {
   198  	return err == nil
   199  }
   200  
   201  // Name returns the name of the CircuitBreaker.
   202  func (cb *CircuitBreaker) Name() string {
   203  	return cb.name
   204  }
   205  
   206  // State returns the current state of the CircuitBreaker.
   207  func (cb *CircuitBreaker) State() BreakerState {
   208  	cb.mutex.Lock()
   209  	defer cb.mutex.Unlock()
   210  
   211  	now := time.Now()
   212  	state, _ := cb.currentState(now)
   213  	return state
   214  }
   215  
   216  // Execute runs the given request if the CircuitBreaker accepts it.
   217  // Execute returns an error instantly if the CircuitBreaker rejects the request.
   218  // Otherwise, Execute returns the result of the request.
   219  // If a panic occurs in the request, the CircuitBreaker handles it as an error
   220  // and causes the same panic again.
   221  func (cb *CircuitBreaker) Execute(req func() (interface{}, error)) (interface{}, error) {
   222  	generation, err := cb.beforeRequest()
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	defer func() {
   228  		e := recover()
   229  		if e != nil {
   230  			cb.afterRequest(generation, false)
   231  			panic(e)
   232  		}
   233  	}()
   234  
   235  	result, err := req()
   236  	cb.afterRequest(generation, cb.isSuccessful(err))
   237  	return result, err
   238  }
   239  
   240  // Name returns the name of the TwoStepCircuitBreaker.
   241  func (cb *TwoStepCircuitBreaker) Name() string {
   242  	return cb.cb.Name()
   243  }
   244  
   245  // State returns the current state of the TwoStepCircuitBreaker.
   246  func (cb *TwoStepCircuitBreaker) State() BreakerState {
   247  	return cb.cb.State()
   248  }
   249  
   250  // Allow checks if a new request can proceed. It returns a callback that should be used to
   251  // register the success or failure in a separate step. If the circuit breaker doesn't allow
   252  // requests, it returns an error.
   253  func (cb *TwoStepCircuitBreaker) Allow() (done func(success bool), err error) {
   254  	generation, err1 := cb.cb.beforeRequest()
   255  	if err1 != nil {
   256  		return nil, err1
   257  	}
   258  
   259  	return func(success bool) {
   260  		cb.cb.afterRequest(generation, success)
   261  	}, nil
   262  }
   263  
   264  func (cb *CircuitBreaker) beforeRequest() (uint64, error) {
   265  	cb.mutex.Lock()
   266  	defer cb.mutex.Unlock()
   267  
   268  	now := time.Now()
   269  	state, generation := cb.currentState(now)
   270  
   271  	if state == BreakerStateOpen {
   272  		return generation, ErrBreakerOpenState
   273  	} else if state == BreakerStateHalfOpen && cb.counts.Requests >= cb.maxRequests {
   274  		return generation, ErrTooManyRequests
   275  	}
   276  
   277  	cb.counts.onRequest()
   278  	return generation, nil
   279  }
   280  
   281  func (cb *CircuitBreaker) afterRequest(before uint64, success bool) {
   282  	cb.mutex.Lock()
   283  	defer cb.mutex.Unlock()
   284  
   285  	now := time.Now()
   286  	state, generation := cb.currentState(now)
   287  	if generation != before {
   288  		return
   289  	}
   290  
   291  	if success {
   292  		cb.onSuccess(state, now)
   293  	} else {
   294  		cb.onFailure(state, now)
   295  	}
   296  }
   297  
   298  func (cb *CircuitBreaker) onSuccess(state BreakerState, now time.Time) {
   299  	switch state {
   300  	case BreakerStateClosed:
   301  		cb.counts.onSuccess()
   302  	case BreakerStateHalfOpen:
   303  		cb.counts.onSuccess()
   304  		if cb.counts.ConsecutiveSuccesses >= cb.maxRequests {
   305  			cb.setState(BreakerStateClosed, now)
   306  		}
   307  	}
   308  }
   309  
   310  func (cb *CircuitBreaker) onFailure(state BreakerState, now time.Time) {
   311  	switch state {
   312  	case BreakerStateClosed:
   313  		cb.counts.onFailure()
   314  		if cb.readyToTrip(cb.counts) {
   315  			cb.setState(BreakerStateOpen, now)
   316  		}
   317  	case BreakerStateHalfOpen:
   318  		cb.setState(BreakerStateOpen, now)
   319  	}
   320  }
   321  
   322  func (cb *CircuitBreaker) currentState(now time.Time) (BreakerState, uint64) {
   323  	switch cb.state {
   324  	case BreakerStateClosed:
   325  		if !cb.expiry.IsZero() && cb.expiry.Before(now) {
   326  			cb.toNewGeneration(now)
   327  		}
   328  	case BreakerStateOpen:
   329  		if cb.expiry.Before(now) {
   330  			cb.setState(BreakerStateHalfOpen, now)
   331  		}
   332  	}
   333  	return cb.state, cb.generation
   334  }
   335  
   336  func (cb *CircuitBreaker) setState(state BreakerState, now time.Time) {
   337  	if cb.state == state {
   338  		return
   339  	}
   340  
   341  	prev := cb.state
   342  	cb.state = state
   343  
   344  	cb.toNewGeneration(now)
   345  
   346  	if cb.onStateChange != nil {
   347  		cb.onStateChange(cb.name, prev, state)
   348  	}
   349  }
   350  
   351  func (cb *CircuitBreaker) toNewGeneration(now time.Time) {
   352  	cb.generation++
   353  	cb.counts.clear()
   354  
   355  	var zero time.Time
   356  	switch cb.state {
   357  	case BreakerStateClosed:
   358  		if cb.interval == 0 {
   359  			cb.expiry = zero
   360  		} else {
   361  			cb.expiry = now.Add(cb.interval)
   362  		}
   363  	case BreakerStateOpen:
   364  		cb.expiry = now.Add(cb.timeout)
   365  	default: // BreakerStateHalfOpen
   366  		cb.expiry = zero
   367  	}
   368  }