github.com/lingyao2333/mo-zero@v1.4.1/core/breaker/breaker.go (about)

     1  package breaker
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/lingyao2333/mo-zero/core/mathx"
    11  	"github.com/lingyao2333/mo-zero/core/proc"
    12  	"github.com/lingyao2333/mo-zero/core/stat"
    13  	"github.com/lingyao2333/mo-zero/core/stringx"
    14  )
    15  
    16  const (
    17  	numHistoryReasons = 5
    18  	timeFormat        = "15:04:05"
    19  )
    20  
    21  // ErrServiceUnavailable is returned when the Breaker state is open.
    22  var ErrServiceUnavailable = errors.New("circuit breaker is open")
    23  
    24  type (
    25  	// Acceptable is the func to check if the error can be accepted.
    26  	Acceptable func(err error) bool
    27  
    28  	// A Breaker represents a circuit breaker.
    29  	Breaker interface {
    30  		// Name returns the name of the Breaker.
    31  		Name() string
    32  
    33  		// Allow checks if the request is allowed.
    34  		// If allowed, a promise will be returned, the caller needs to call promise.Accept()
    35  		// on success, or call promise.Reject() on failure.
    36  		// If not allow, ErrServiceUnavailable will be returned.
    37  		Allow() (Promise, error)
    38  
    39  		// Do runs the given request if the Breaker accepts it.
    40  		// Do returns an error instantly if the Breaker rejects the request.
    41  		// If a panic occurs in the request, the Breaker handles it as an error
    42  		// and causes the same panic again.
    43  		Do(req func() error) error
    44  
    45  		// DoWithAcceptable runs the given request if the Breaker accepts it.
    46  		// DoWithAcceptable returns an error instantly if the Breaker rejects the request.
    47  		// If a panic occurs in the request, the Breaker handles it as an error
    48  		// and causes the same panic again.
    49  		// acceptable checks if it's a successful call, even if the err is not nil.
    50  		DoWithAcceptable(req func() error, acceptable Acceptable) error
    51  
    52  		// DoWithFallback runs the given request if the Breaker accepts it.
    53  		// DoWithFallback runs the fallback if the Breaker rejects the request.
    54  		// If a panic occurs in the request, the Breaker handles it as an error
    55  		// and causes the same panic again.
    56  		DoWithFallback(req func() error, fallback func(err error) error) error
    57  
    58  		// DoWithFallbackAcceptable runs the given request if the Breaker accepts it.
    59  		// DoWithFallbackAcceptable runs the fallback if the Breaker rejects the request.
    60  		// If a panic occurs in the request, the Breaker handles it as an error
    61  		// and causes the same panic again.
    62  		// acceptable checks if it's a successful call, even if the err is not nil.
    63  		DoWithFallbackAcceptable(req func() error, fallback func(err error) error, acceptable Acceptable) error
    64  	}
    65  
    66  	// Option defines the method to customize a Breaker.
    67  	Option func(breaker *circuitBreaker)
    68  
    69  	// Promise interface defines the callbacks that returned by Breaker.Allow.
    70  	Promise interface {
    71  		// Accept tells the Breaker that the call is successful.
    72  		Accept()
    73  		// Reject tells the Breaker that the call is failed.
    74  		Reject(reason string)
    75  	}
    76  
    77  	internalPromise interface {
    78  		Accept()
    79  		Reject()
    80  	}
    81  
    82  	circuitBreaker struct {
    83  		name string
    84  		throttle
    85  	}
    86  
    87  	internalThrottle interface {
    88  		allow() (internalPromise, error)
    89  		doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error
    90  	}
    91  
    92  	throttle interface {
    93  		allow() (Promise, error)
    94  		doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error
    95  	}
    96  )
    97  
    98  // NewBreaker returns a Breaker object.
    99  // opts can be used to customize the Breaker.
   100  func NewBreaker(opts ...Option) Breaker {
   101  	var b circuitBreaker
   102  	for _, opt := range opts {
   103  		opt(&b)
   104  	}
   105  	if len(b.name) == 0 {
   106  		b.name = stringx.Rand()
   107  	}
   108  	b.throttle = newLoggedThrottle(b.name, newGoogleBreaker())
   109  
   110  	return &b
   111  }
   112  
   113  func (cb *circuitBreaker) Allow() (Promise, error) {
   114  	return cb.throttle.allow()
   115  }
   116  
   117  func (cb *circuitBreaker) Do(req func() error) error {
   118  	return cb.throttle.doReq(req, nil, defaultAcceptable)
   119  }
   120  
   121  func (cb *circuitBreaker) DoWithAcceptable(req func() error, acceptable Acceptable) error {
   122  	return cb.throttle.doReq(req, nil, acceptable)
   123  }
   124  
   125  func (cb *circuitBreaker) DoWithFallback(req func() error, fallback func(err error) error) error {
   126  	return cb.throttle.doReq(req, fallback, defaultAcceptable)
   127  }
   128  
   129  func (cb *circuitBreaker) DoWithFallbackAcceptable(req func() error, fallback func(err error) error,
   130  	acceptable Acceptable) error {
   131  	return cb.throttle.doReq(req, fallback, acceptable)
   132  }
   133  
   134  func (cb *circuitBreaker) Name() string {
   135  	return cb.name
   136  }
   137  
   138  // WithName returns a function to set the name of a Breaker.
   139  func WithName(name string) Option {
   140  	return func(b *circuitBreaker) {
   141  		b.name = name
   142  	}
   143  }
   144  
   145  func defaultAcceptable(err error) bool {
   146  	return err == nil
   147  }
   148  
   149  type loggedThrottle struct {
   150  	name string
   151  	internalThrottle
   152  	errWin *errorWindow
   153  }
   154  
   155  func newLoggedThrottle(name string, t internalThrottle) loggedThrottle {
   156  	return loggedThrottle{
   157  		name:             name,
   158  		internalThrottle: t,
   159  		errWin:           new(errorWindow),
   160  	}
   161  }
   162  
   163  func (lt loggedThrottle) allow() (Promise, error) {
   164  	promise, err := lt.internalThrottle.allow()
   165  	return promiseWithReason{
   166  		promise: promise,
   167  		errWin:  lt.errWin,
   168  	}, lt.logError(err)
   169  }
   170  
   171  func (lt loggedThrottle) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error {
   172  	return lt.logError(lt.internalThrottle.doReq(req, fallback, func(err error) bool {
   173  		accept := acceptable(err)
   174  		if !accept && err != nil {
   175  			lt.errWin.add(err.Error())
   176  		}
   177  		return accept
   178  	}))
   179  }
   180  
   181  func (lt loggedThrottle) logError(err error) error {
   182  	if err == ErrServiceUnavailable {
   183  		// if circuit open, not possible to have empty error window
   184  		stat.Report(fmt.Sprintf(
   185  			"proc(%s/%d), callee: %s, breaker is open and requests dropped\nlast errors:\n%s",
   186  			proc.ProcessName(), proc.Pid(), lt.name, lt.errWin))
   187  	}
   188  
   189  	return err
   190  }
   191  
   192  type errorWindow struct {
   193  	reasons [numHistoryReasons]string
   194  	index   int
   195  	count   int
   196  	lock    sync.Mutex
   197  }
   198  
   199  func (ew *errorWindow) add(reason string) {
   200  	ew.lock.Lock()
   201  	ew.reasons[ew.index] = fmt.Sprintf("%s %s", time.Now().Format(timeFormat), reason)
   202  	ew.index = (ew.index + 1) % numHistoryReasons
   203  	ew.count = mathx.MinInt(ew.count+1, numHistoryReasons)
   204  	ew.lock.Unlock()
   205  }
   206  
   207  func (ew *errorWindow) String() string {
   208  	var reasons []string
   209  
   210  	ew.lock.Lock()
   211  	// reverse order
   212  	for i := ew.index - 1; i >= ew.index-ew.count; i-- {
   213  		reasons = append(reasons, ew.reasons[(i+numHistoryReasons)%numHistoryReasons])
   214  	}
   215  	ew.lock.Unlock()
   216  
   217  	return strings.Join(reasons, "\n")
   218  }
   219  
   220  type promiseWithReason struct {
   221  	promise internalPromise
   222  	errWin  *errorWindow
   223  }
   224  
   225  func (p promiseWithReason) Accept() {
   226  	p.promise.Accept()
   227  }
   228  
   229  func (p promiseWithReason) Reject(reason string) {
   230  	p.errWin.add(reason)
   231  	p.promise.Reject()
   232  }