Created
May 12, 2022 13:48
-
-
Save williammartin/6929174cc4559ea53b4c1b64d2cdb57b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package retry | |
import ( | |
"context" | |
"errors" | |
"fmt" | |
"math" | |
"time" | |
) | |
type DelayFunc func(attempt uint8) time.Duration | |
type Config struct { | |
limit uint8 | |
delayFunc DelayFunc | |
ctx context.Context | |
} | |
type RetryOption func(*Config) | |
type Retryable func() error | |
type irrecoverable interface { | |
Irrecoverable() bool | |
} | |
func IsIrrecoverable(err error) bool { | |
var te irrecoverable | |
ok := errors.As(err, &te) | |
return ok && te.Irrecoverable() | |
} | |
func Do(retryable Retryable, opts ...RetryOption) error { | |
const defaultLimit uint8 = 3 | |
config := &Config{ | |
limit: defaultLimit, | |
delayFunc: func(attempt uint8) time.Duration { | |
return 0 | |
}, | |
ctx: context.Background(), | |
} | |
for _, opt := range opts { | |
opt(config) | |
} | |
attempts := uint8(0) | |
for { | |
err := retryable() | |
if err == nil { | |
return nil | |
} | |
if IsIrrecoverable(err) { | |
return err | |
} | |
attempts++ | |
if attempts >= config.limit { | |
return err | |
} | |
select { | |
case <-config.ctx.Done(): | |
return fmt.Errorf("context cancelled") | |
case <-time.After(config.delayFunc(attempts)): | |
// Do nothing intentionally | |
} | |
} | |
} | |
func WithRetryLimit(limit uint8) RetryOption { | |
return func(config *Config) { | |
config.limit = limit | |
} | |
} | |
func WithDelayFunc(delayFunc DelayFunc) RetryOption { | |
return func(config *Config) { | |
config.delayFunc = delayFunc | |
} | |
} | |
func FixedDelay(delay time.Duration) DelayFunc { | |
return func(attempt uint8) time.Duration { | |
return delay | |
} | |
} | |
func ExponentialDelay(delay time.Duration) DelayFunc { | |
const exponent = 2 | |
return func(attempt uint8) time.Duration { | |
return time.Duration(float64(delay) * math.Pow(exponent, float64(attempt-1))) | |
} | |
} | |
func WithContext(ctx context.Context) RetryOption { | |
return func(config *Config) { | |
config.ctx = ctx | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment