github.com/msales/pkg/v3@v3.24.0/breaker/breaker.go (about)

     1  package breaker
     2  
     3  import (
     4  	"errors"
     5  	"sync"
     6  	"time"
     7  )
     8  
     9  // State represents a Breakers state.
    10  type State int8
    11  
    12  // State constants for the Breaker.
    13  const (
    14  	StateClosed State = iota
    15  	StateHalfOpen
    16  	StateOpen
    17  )
    18  
    19  var (
    20  	// ErrOpenState is the error returned when the Breaker is open.
    21  	ErrOpenState = errors.New("breaker: circuit breaker is open")
    22  
    23  	// ErrTooManyRequests is the error returned when too many test requests are
    24  	// made when the Breaker is half-open.
    25  	ErrTooManyRequests = errors.New("breaker: too many requests")
    26  )
    27  
    28  // Counter holds the number number of requests, successes and failures of a breaker.
    29  //
    30  // Counter is reset from time to time, and totals should not be used as a full totals.
    31  type Counter struct {
    32  	// Requests is the total number of requests made.
    33  	Requests             uint64
    34  	// Successes is the total number of successes returned.
    35  	Successes            uint64
    36  	// Failures is the total number of failures returned.
    37  	Failures             uint64
    38  	// ConsecutiveSuccesses is the number of consecutive successes returned.
    39  	ConsecutiveSuccesses uint64
    40  	// ConsecutiveFailures is the number of consecutive failures returned.
    41  	ConsecutiveFailures  uint64
    42  }
    43  
    44  func (c *Counter) reset() {
    45  	c.Requests = 0
    46  	c.Successes = 0
    47  	c.Failures = 0
    48  	c.ConsecutiveSuccesses = 0
    49  	c.ConsecutiveFailures = 0
    50  }
    51  
    52  func (c *Counter) request() {
    53  	c.Requests++
    54  }
    55  
    56  func (c *Counter) success() {
    57  	c.Successes++
    58  	c.ConsecutiveSuccesses++
    59  	c.ConsecutiveFailures = 0
    60  }
    61  
    62  func (c *Counter) failure() {
    63  	c.Failures++
    64  	c.ConsecutiveFailures++
    65  	c.ConsecutiveSuccesses = 0
    66  }
    67  
    68  // Fuse represents a Breaker fuse used to trip the breaker.
    69  type Fuse interface {
    70  	// Trip determines if the Breaker should be tripped.
    71  	Trip(Counter) bool
    72  }
    73  
    74  // FuseFunc is an adapter allowing to use a function as a Fuse.
    75  type FuseFunc func(Counter) bool
    76  
    77  // Trip determines if the Breaker should be tripped.
    78  func (f FuseFunc) Trip(c Counter) bool {
    79  	return f(c)
    80  }
    81  
    82  // ThresholdFuse trips the Breaker when the total number of failures exceeds the given count.
    83  func ThresholdFuse(count uint64) FuseFunc {
    84  	return FuseFunc(func(c Counter) bool {
    85  		return c.Failures > count
    86  	})
    87  }
    88  
    89  // ConsecutiveFuse trips the Breaker when the consecutive number of failures exceeds the given count.
    90  func ConsecutiveFuse(count uint64) FuseFunc {
    91  	return FuseFunc(func(c Counter) bool {
    92  		return c.ConsecutiveFailures > count
    93  	})
    94  }
    95  
    96  // RateFuse trips the Breaker when the percentage of failures exceeds the given rate.
    97  //
    98  // rate should be between 0 and 100.
    99  func RateFuse(rate uint64) FuseFunc {
   100  	if rate >= 100 {
   101  		panic(errors.New("breaker: rate should be less than 100 percent"))
   102  	}
   103  
   104  	return FuseFunc(func(c Counter) bool {
   105  		return c.Failures/c.Requests*100 > rate
   106  	})
   107  }
   108  
   109  // OptFunc represents a configuration function for Breaker.
   110  type OptFunc func(b *Breaker)
   111  
   112  // WithSleep sets the time the Breaker stays open for.
   113  func WithSleep(d time.Duration) OptFunc {
   114  	return OptFunc(func(b *Breaker) {
   115  		b.sleep = d
   116  	})
   117  }
   118  
   119  // WithTestRequests sets the number of test requests allowed when the Breaker is half-open.
   120  func WithTestRequests(c uint64) OptFunc {
   121  	return OptFunc(func(b *Breaker) {
   122  		b.testRequests = c
   123  	})
   124  }
   125  
   126  // Breaker is a circuit breaker.
   127  type Breaker struct {
   128  	fuse         Fuse
   129  	sleep        time.Duration
   130  	testRequests uint64
   131  
   132  	mu        sync.Mutex
   133  	state     State
   134  	counter   Counter
   135  	openUntil time.Time
   136  }
   137  
   138  // NewBreaker creates a new Breaker.
   139  func NewBreaker(f Fuse, opts ...OptFunc) *Breaker {
   140  	b := &Breaker{
   141  		fuse:         f,
   142  		sleep:        10 * time.Second,
   143  		testRequests: 1,
   144  	}
   145  
   146  	for _, opt := range opts {
   147  		opt(b)
   148  	}
   149  
   150  	return b
   151  }
   152  
   153  // Run runs the given request if the Breaker allows it.
   154  //
   155  // Run returns an error immediately if the Breaker rejects the request,
   156  // otherwise it returns the result of the request.
   157  func (b *Breaker) Run(req func() error) error {
   158  	if err := b.canRun(); err != nil {
   159  		return err
   160  	}
   161  
   162  	defer func() {
   163  		if r := recover(); r != nil {
   164  			b.handleResult(false)
   165  			panic(r)
   166  		}
   167  	}()
   168  
   169  	err := req()
   170  
   171  	b.handleResult(err == nil)
   172  
   173  	return err
   174  }
   175  
   176  func (b *Breaker) canRun() error {
   177  	b.mu.Lock()
   178  	defer b.mu.Unlock()
   179  
   180  	state := b.getState()
   181  
   182  	if state == StateOpen {
   183  		return ErrOpenState
   184  	} else if state == StateHalfOpen && b.counter.Requests >= b.testRequests {
   185  		return ErrTooManyRequests
   186  	}
   187  
   188  	b.counter.request()
   189  
   190  	return nil
   191  }
   192  
   193  func (b *Breaker) handleResult(success bool) {
   194  	b.mu.Lock()
   195  	defer b.mu.Unlock()
   196  
   197  	state := b.getState()
   198  
   199  	if !success {
   200  		b.handleFailure(state)
   201  		return
   202  	}
   203  
   204  	b.handleSuccess(state)
   205  }
   206  
   207  func (b *Breaker) handleSuccess(state State) {
   208  	switch state {
   209  	case StateClosed:
   210  		b.counter.success()
   211  
   212  	case StateHalfOpen:
   213  		b.counter.success()
   214  		if state == StateHalfOpen && b.counter.ConsecutiveSuccesses >= b.testRequests {
   215  			b.setState(StateClosed)
   216  		}
   217  	}
   218  }
   219  
   220  func (b *Breaker) handleFailure(state State) {
   221  	switch state {
   222  	case StateClosed:
   223  		b.counter.failure()
   224  		if b.fuse.Trip(b.counter) {
   225  			b.setState(StateOpen)
   226  		}
   227  
   228  	case StateHalfOpen:
   229  		b.setState(StateOpen)
   230  	}
   231  }
   232  
   233  // State returns the state of the Breaker.
   234  func (b *Breaker) State() State {
   235  	b.mu.Lock()
   236  	defer b.mu.Unlock()
   237  
   238  	return b.getState()
   239  }
   240  
   241  func (b *Breaker) getState() State {
   242  	now := time.Now()
   243  
   244  	switch b.state {
   245  	case StateClosed:
   246  		if !b.openUntil.IsZero() && b.openUntil.Before(now) {
   247  			b.openUntil = time.Time{}
   248  		}
   249  
   250  	case StateOpen:
   251  		if b.openUntil.Before(now) {
   252  			b.setState(StateHalfOpen)
   253  		}
   254  	}
   255  	return b.state
   256  }
   257  
   258  func (b *Breaker) setState(state State) {
   259  	b.state = state
   260  
   261  	if b.state == StateOpen {
   262  		b.openUntil = time.Now().Add(b.sleep)
   263  	}
   264  
   265  	b.counter.reset()
   266  }