github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/utils/retry/retry.go (about)

     1  // Package retry implements frequently used retry strategies and options.
     2  package retry
     3  
     4  import "time"
     5  
     6  // Stop is used to indicate the retry function to stop retry.
     7  type Stop struct {
     8  	Err error
     9  }
    10  
    11  func (e Stop) Error() string {
    12  	return e.Err.Error()
    13  }
    14  
    15  // Default will call param function f at most 3 times before returning error.
    16  // Between each retry will sleep an exponential time starts at 500ms.
    17  // In case of all retry fails, the total sleep time will be about 750ms - 2250ms.
    18  //
    19  // It is shorthand for Retry(3, 500*time.Millisecond, f).
    20  func Default(f func() error, opts ...Option) Result {
    21  	return Retry(3, 500*time.Millisecond, f, opts...)
    22  }
    23  
    24  // Retry retry the target function with exponential sleep time.
    25  // It implements algorithm described in https://upgear.io/blog/simple-golang-retry-function/.
    26  func Retry(attempts int, sleep time.Duration, f func() error, opts ...Option) Result {
    27  	opt := defaultOptions
    28  	opt.Attempts = attempts
    29  	opt.Sleep = sleep
    30  	return retry(opt, f, opts...)
    31  }
    32  
    33  // Const retry the target function with constant sleep time.
    34  // It is shorthand for Retry(attempts, sleep, f, C()).
    35  func Const(attempts int, sleep time.Duration, f func() error, opts ...Option) Result {
    36  	opt := defaultOptions
    37  	opt.Attempts = attempts
    38  	opt.Sleep = sleep
    39  	opt.Strategy = constant
    40  	return retry(opt, f, opts...)
    41  }
    42  
    43  // Linear retry the target function with linear sleep time.
    44  // It is shorthand for Retry(attempts, sleep, f, L(sleep)).
    45  func Linear(attempts int, sleep time.Duration, f func() error, opts ...Option) Result {
    46  	opt := defaultOptions
    47  	opt.Attempts = attempts
    48  	opt.Sleep = sleep
    49  	opt.Strategy = linear{sleep}.next
    50  	return retry(opt, f, opts...)
    51  }
    52  
    53  // Forever retry the target function endlessly if it returns error.
    54  // To stop the the retry loop on error, the target function should return Stop.
    55  //
    56  // The caller should take care of dead loop.
    57  func Forever(sleep, maxSleep time.Duration, f func() error, opts ...Option) Result {
    58  	opt := defaultOptions
    59  	opt.Sleep = sleep
    60  	opt.MaxSleep = maxSleep
    61  	return retry(opt, f, opts...)
    62  }
    63  
    64  // retry do the retry job according given options.
    65  func retry(opt options, f func() error, opts ...Option) (r Result) {
    66  	for _, o := range opts {
    67  		opt = o(opt)
    68  	}
    69  
    70  	var err error
    71  	r.Attempts++
    72  	if err = f(); err == nil {
    73  		r.Ok = true
    74  		if opt.Breaker != nil {
    75  			opt.Breaker.succ.incr(time.Now().Unix())
    76  		}
    77  		return r
    78  	}
    79  
    80  	var merr = NewSizedError(opt.MaxErrors)
    81  	var sleep = opt.Sleep
    82  	for {
    83  		if _, ok := err.(Stop); ok {
    84  			break
    85  		}
    86  		// attempts <= 0 means retry forever.
    87  		if opt.Attempts > 0 && r.Attempts >= opt.Attempts {
    88  			break
    89  		}
    90  		// check sliding window breaker
    91  		if opt.Breaker != nil && !opt.Breaker.shouldRetry() {
    92  			break
    93  		}
    94  
    95  		merr.Append(err)
    96  		opt.Hook(r.Attempts, err)
    97  		if opt.Breaker != nil {
    98  			opt.Breaker.fail.incr(time.Now().Unix())
    99  		}
   100  
   101  		if opt.MaxSleep > 0 && sleep > opt.MaxSleep {
   102  			sleep = opt.MaxSleep
   103  		}
   104  		if opt.Jitter == nil {
   105  			time.Sleep(sleep)
   106  		} else {
   107  			time.Sleep(opt.Jitter(sleep))
   108  		}
   109  		r.Attempts++
   110  		if err = f(); err == nil {
   111  			r.Ok = true
   112  			if opt.Breaker != nil {
   113  				opt.Breaker.succ.incr(time.Now().Unix())
   114  			}
   115  			break
   116  		}
   117  		sleep = opt.Strategy(sleep)
   118  	}
   119  	if err != nil {
   120  		if s, ok := err.(Stop); ok {
   121  			// Return the original error for later checking.
   122  			merr.Append(s.Err)
   123  			opt.Hook(r.Attempts, s.Err)
   124  
   125  			// Stop error from caller don't count for circuit breaker.
   126  		} else {
   127  			merr.Append(err)
   128  			opt.Hook(r.Attempts, err)
   129  			if opt.Breaker != nil {
   130  				opt.Breaker.fail.incr(time.Now().Unix())
   131  			}
   132  		}
   133  	}
   134  	r.Error = merr.ErrOrNil()
   135  	return r
   136  }
   137  
   138  type Result struct {
   139  	Ok       bool
   140  	Attempts int
   141  	Error    error
   142  }