194 lines
4.4 KiB
Go
194 lines
4.4 KiB
Go
package retry
|
|
|
|
import (
|
|
"context"
|
|
"math"
|
|
"math/rand"
|
|
"time"
|
|
)
|
|
|
|
// Function signature of retry if function
|
|
type RetryIfFunc func(error) bool
|
|
|
|
// Function signature of OnRetry function
|
|
// n = count of attempts
|
|
type OnRetryFunc func(n uint, err error)
|
|
|
|
type DelayTypeFunc func(n uint, config *Config) time.Duration
|
|
|
|
type Config struct {
|
|
attempts uint
|
|
delay time.Duration
|
|
maxDelay time.Duration
|
|
maxJitter time.Duration
|
|
onRetry OnRetryFunc
|
|
retryIf RetryIfFunc
|
|
delayType DelayTypeFunc
|
|
lastErrorOnly bool
|
|
context context.Context
|
|
|
|
maxBackOffN uint
|
|
}
|
|
|
|
// Option represents an option for retry.
|
|
type Option func(*Config)
|
|
|
|
// return the direct last error that came from the retried function
|
|
// default is false (return wrapped errors with everything)
|
|
func LastErrorOnly(lastErrorOnly bool) Option {
|
|
return func(c *Config) {
|
|
c.lastErrorOnly = lastErrorOnly
|
|
}
|
|
}
|
|
|
|
// Attempts set count of retry
|
|
// default is 10
|
|
func Attempts(attempts uint) Option {
|
|
return func(c *Config) {
|
|
c.attempts = attempts
|
|
}
|
|
}
|
|
|
|
// Delay set delay between retry
|
|
// default is 100ms
|
|
func Delay(delay time.Duration) Option {
|
|
return func(c *Config) {
|
|
c.delay = delay
|
|
}
|
|
}
|
|
|
|
// MaxDelay set maximum delay between retry
|
|
// does not apply by default
|
|
func MaxDelay(maxDelay time.Duration) Option {
|
|
return func(c *Config) {
|
|
c.maxDelay = maxDelay
|
|
}
|
|
}
|
|
|
|
// MaxJitter sets the maximum random Jitter between retries for RandomDelay
|
|
func MaxJitter(maxJitter time.Duration) Option {
|
|
return func(c *Config) {
|
|
c.maxJitter = maxJitter
|
|
}
|
|
}
|
|
|
|
// DelayType set type of the delay between retries
|
|
// default is BackOff
|
|
func DelayType(delayType DelayTypeFunc) Option {
|
|
return func(c *Config) {
|
|
c.delayType = delayType
|
|
}
|
|
}
|
|
|
|
// BackOffDelay is a DelayType which increases delay between consecutive retries
|
|
func BackOffDelay(n uint, config *Config) time.Duration {
|
|
// 1 << 63 would overflow signed int64 (time.Duration), thus 62.
|
|
const max uint = 62
|
|
|
|
if config.maxBackOffN == 0 {
|
|
if config.delay <= 0 {
|
|
config.delay = 1
|
|
}
|
|
config.maxBackOffN = max - uint(math.Floor(math.Log2(float64(config.delay))))
|
|
}
|
|
|
|
if n > config.maxBackOffN {
|
|
n = config.maxBackOffN
|
|
}
|
|
return config.delay << n
|
|
}
|
|
|
|
// FixedDelay is a DelayType which keeps delay the same through all iterations
|
|
func FixedDelay(_ uint, config *Config) time.Duration {
|
|
return config.delay
|
|
}
|
|
|
|
// RandomDelay is a DelayType which picks a random delay up to config.maxJitter
|
|
func RandomDelay(_ uint, config *Config) time.Duration {
|
|
return time.Duration(rand.Int63n(int64(config.maxJitter)))
|
|
}
|
|
|
|
// CombineDelay is a DelayType the combines all of the specified delays into a new DelayTypeFunc
|
|
func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc {
|
|
const maxInt64 = uint64(math.MaxInt64)
|
|
|
|
return func(n uint, config *Config) time.Duration {
|
|
var total uint64
|
|
for _, delay := range delays {
|
|
total += uint64(delay(n, config))
|
|
if total > maxInt64 {
|
|
total = maxInt64
|
|
}
|
|
}
|
|
return time.Duration(total)
|
|
}
|
|
}
|
|
|
|
// OnRetry function callback are called each retry
|
|
//
|
|
// log each retry example:
|
|
//
|
|
// retry.Do(
|
|
// func() error {
|
|
// return errors.New("some error")
|
|
// },
|
|
// retry.OnRetry(func(n uint, err error) {
|
|
// log.Printf("#%d: %s\n", n, err)
|
|
// }),
|
|
// )
|
|
func OnRetry(onRetry OnRetryFunc) Option {
|
|
return func(c *Config) {
|
|
c.onRetry = onRetry
|
|
}
|
|
}
|
|
|
|
// RetryIf controls whether a retry should be attempted after an error
|
|
// (assuming there are any retry attempts remaining)
|
|
//
|
|
// skip retry if special error example:
|
|
//
|
|
// retry.Do(
|
|
// func() error {
|
|
// return errors.New("special error")
|
|
// },
|
|
// retry.RetryIf(func(err error) bool {
|
|
// if err.Error() == "special error" {
|
|
// return false
|
|
// }
|
|
// return true
|
|
// })
|
|
// )
|
|
//
|
|
// By default RetryIf stops execution if the error is wrapped using `retry.Unrecoverable`,
|
|
// so above example may also be shortened to:
|
|
//
|
|
// retry.Do(
|
|
// func() error {
|
|
// return retry.Unrecoverable(errors.New("special error"))
|
|
// }
|
|
// )
|
|
func RetryIf(retryIf RetryIfFunc) Option {
|
|
return func(c *Config) {
|
|
c.retryIf = retryIf
|
|
}
|
|
}
|
|
|
|
// Context allow to set context of retry
|
|
// default are Background context
|
|
//
|
|
// example of immediately cancellation (maybe it isn't the best example, but it describes behavior enough; I hope)
|
|
// ctx, cancel := context.WithCancel(context.Background())
|
|
// cancel()
|
|
//
|
|
// retry.Do(
|
|
// func() error {
|
|
// ...
|
|
// },
|
|
// retry.Context(ctx),
|
|
// )
|
|
func Context(ctx context.Context) Option {
|
|
return func(c *Config) {
|
|
c.context = ctx
|
|
}
|
|
}
|