git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/retry/options.go (about) 1 package retry 2 3 import ( 4 "context" 5 "math" 6 "math/rand" 7 "time" 8 ) 9 10 // Function signature of retry if function 11 type RetryIfFunc func(error) bool 12 13 // Function signature of OnRetry function 14 // n = count of attempts 15 type OnRetryFunc func(n uint, err error) 16 17 // DelayTypeFunc is called to return the next delay to wait after the retriable function fails on `err` after `n` attempts. 18 type DelayTypeFunc func(n uint, err error, config *Config) time.Duration 19 20 type Config struct { 21 attempts uint 22 delay time.Duration 23 maxDelay time.Duration 24 maxJitter time.Duration 25 onRetry OnRetryFunc 26 retryIf RetryIfFunc 27 delayType DelayTypeFunc 28 lastErrorOnly bool 29 context context.Context 30 31 maxBackOffN uint 32 } 33 34 // Option represents an option for retry. 35 type Option func(*Config) 36 37 func emptyOption(c *Config) {} 38 39 // return the direct last error that came from the retried function 40 // default is false (return wrapped errors with everything) 41 func LastErrorOnly(lastErrorOnly bool) Option { 42 return func(c *Config) { 43 c.lastErrorOnly = lastErrorOnly 44 } 45 } 46 47 // Attempts set count of retry. Setting to 0 will retry until the retried function succeeds. 48 // default is 10 49 func Attempts(attempts uint) Option { 50 return func(c *Config) { 51 c.attempts = attempts 52 } 53 } 54 55 // Delay set delay between retry 56 // default is 100ms 57 func Delay(delay time.Duration) Option { 58 return func(c *Config) { 59 c.delay = delay 60 } 61 } 62 63 // MaxDelay set maximum delay between retry 64 // does not apply by default 65 func MaxDelay(maxDelay time.Duration) Option { 66 return func(c *Config) { 67 c.maxDelay = maxDelay 68 } 69 } 70 71 // MaxJitter sets the maximum random Jitter between retries for RandomDelay 72 func MaxJitter(maxJitter time.Duration) Option { 73 return func(c *Config) { 74 c.maxJitter = maxJitter 75 } 76 } 77 78 // DelayType set type of the delay between retries 79 // default is BackOff 80 func DelayType(delayType DelayTypeFunc) Option { 81 if delayType == nil { 82 return emptyOption 83 } 84 return func(c *Config) { 85 c.delayType = delayType 86 } 87 } 88 89 // BackOffDelay is a DelayType which increases delay between consecutive retries 90 func BackOffDelay(n uint, _ error, config *Config) time.Duration { 91 // 1 << 63 would overflow signed int64 (time.Duration), thus 62. 92 const max uint = 62 93 94 if config.maxBackOffN == 0 { 95 if config.delay <= 0 { 96 config.delay = 1 97 } 98 99 config.maxBackOffN = max - uint(math.Floor(math.Log2(float64(config.delay)))) 100 } 101 102 if n > config.maxBackOffN { 103 n = config.maxBackOffN 104 } 105 106 return config.delay << n 107 } 108 109 // FixedDelay is a DelayType which keeps delay the same through all iterations 110 func FixedDelay(_ uint, _ error, config *Config) time.Duration { 111 return config.delay 112 } 113 114 // RandomDelay is a DelayType which picks a random delay up to config.maxJitter 115 func RandomDelay(_ uint, _ error, config *Config) time.Duration { 116 return time.Duration(rand.Int63n(int64(config.maxJitter))) 117 } 118 119 // CombineDelay is a DelayType the combines all of the specified delays into a new DelayTypeFunc 120 func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc { 121 const maxInt64 = uint64(math.MaxInt64) 122 123 return func(n uint, err error, config *Config) time.Duration { 124 var total uint64 125 for _, delay := range delays { 126 total += uint64(delay(n, err, config)) 127 if total > maxInt64 { 128 total = maxInt64 129 } 130 } 131 132 return time.Duration(total) 133 } 134 } 135 136 // OnRetry function callback are called each retry 137 // 138 // log each retry example: 139 // 140 // retry.Do( 141 // func() error { 142 // return errors.New("some error") 143 // }, 144 // retry.OnRetry(func(n uint, err error) { 145 // log.Printf("#%d: %s\n", n, err) 146 // }), 147 // ) 148 func OnRetry(onRetry OnRetryFunc) Option { 149 if onRetry == nil { 150 return emptyOption 151 } 152 return func(c *Config) { 153 c.onRetry = onRetry 154 } 155 } 156 157 // RetryIf controls whether a retry should be attempted after an error 158 // (assuming there are any retry attempts remaining) 159 // 160 // skip retry if special error example: 161 // 162 // retry.Do( 163 // func() error { 164 // return errors.New("special error") 165 // }, 166 // retry.RetryIf(func(err error) bool { 167 // if err.Error() == "special error" { 168 // return false 169 // } 170 // return true 171 // }) 172 // ) 173 // 174 // By default RetryIf stops execution if the error is wrapped using `retry.Unrecoverable`, 175 // so above example may also be shortened to: 176 // 177 // retry.Do( 178 // func() error { 179 // return retry.Unrecoverable(errors.New("special error")) 180 // } 181 // ) 182 func RetryIf(retryIf RetryIfFunc) Option { 183 if retryIf == nil { 184 return emptyOption 185 } 186 return func(c *Config) { 187 c.retryIf = retryIf 188 } 189 } 190 191 // Context allow to set context of retry 192 // default are Background context 193 // 194 // example of immediately cancellation (maybe it isn't the best example, but it describes behavior enough; I hope) 195 // 196 // ctx, cancel := context.WithCancel(context.Background()) 197 // cancel() 198 // 199 // retry.Do( 200 // func() error { 201 // ... 202 // }, 203 // retry.Context(ctx), 204 // ) 205 func Context(ctx context.Context) Option { 206 return func(c *Config) { 207 c.context = ctx 208 } 209 }