package retry
import (
)
type BackoffDelayer interface {
BackoffDelay(attempt int, err error) (time.Duration, error)
}
type BackoffDelayerFunc func(int, error) (time.Duration, error)
func ( BackoffDelayerFunc) ( int, error) (time.Duration, error) {
return (, )
}
const (
DefaultMaxAttempts int = 3
DefaultMaxBackoff time.Duration = 20 * time.Second
)
const (
DefaultRetryRateTokens uint = 500
DefaultRetryCost uint = 5
DefaultRetryTimeoutCost uint = 10
DefaultNoRetryIncrement uint = 1
)
var DefaultRetryableHTTPStatusCodes = map[int]struct{}{
500: {},
502: {},
503: {},
504: {},
}
var DefaultRetryableErrorCodes = map[string]struct{}{
"RequestTimeout": {},
"RequestTimeoutException": {},
}
var DefaultThrottleErrorCodes = map[string]struct{}{
"Throttling": {},
"ThrottlingException": {},
"ThrottledException": {},
"RequestThrottledException": {},
"TooManyRequestsException": {},
"ProvisionedThroughputExceededException": {},
"TransactionInProgressException": {},
"RequestLimitExceeded": {},
"BandwidthLimitExceeded": {},
"LimitExceededException": {},
"RequestThrottled": {},
"SlowDown": {},
"PriorRequestNotComplete": {},
"EC2ThrottledException": {},
}
var DefaultRetryables = []IsErrorRetryable{
NoRetryCanceledError{},
RetryableError{},
RetryableConnectionError{},
RetryableHTTPStatusCode{
Codes: DefaultRetryableHTTPStatusCodes,
},
RetryableErrorCode{
Codes: DefaultRetryableErrorCodes,
},
RetryableErrorCode{
Codes: DefaultThrottleErrorCodes,
},
}
var DefaultTimeouts = []IsErrorTimeout{
TimeouterError{},
}
type StandardOptions struct {
MaxAttempts int
MaxBackoff time.Duration
Backoff BackoffDelayer
Retryables []IsErrorRetryable
Timeouts []IsErrorTimeout
RateLimiter RateLimiter
RetryCost uint
RetryTimeoutCost uint
NoRetryIncrement uint
}
type RateLimiter interface {
GetToken(ctx context.Context, cost uint) (releaseToken func() error, err error)
AddTokens(uint) error
}
type Standard struct {
options StandardOptions
timeout IsErrorTimeout
retryable IsErrorRetryable
backoff BackoffDelayer
}
func ( ...func(*StandardOptions)) *Standard {
:= StandardOptions{
MaxAttempts: DefaultMaxAttempts,
MaxBackoff: DefaultMaxBackoff,
Retryables: append([]IsErrorRetryable{}, DefaultRetryables...),
Timeouts: append([]IsErrorTimeout{}, DefaultTimeouts...),
RateLimiter: ratelimit.NewTokenRateLimit(DefaultRetryRateTokens),
RetryCost: DefaultRetryCost,
RetryTimeoutCost: DefaultRetryTimeoutCost,
NoRetryIncrement: DefaultNoRetryIncrement,
}
for , := range {
(&)
}
if .MaxAttempts <= 0 {
.MaxAttempts = DefaultMaxAttempts
}
:= .Backoff
if == nil {
= NewExponentialJitterBackoff(.MaxBackoff)
}
return &Standard{
options: ,
backoff: ,
retryable: IsErrorRetryables(.Retryables),
timeout: IsErrorTimeouts(.Timeouts),
}
}
func ( *Standard) () int {
return .options.MaxAttempts
}
func ( *Standard) ( error) bool {
return .retryable.IsErrorRetryable().Bool()
}
func ( *Standard) ( int, error) (time.Duration, error) {
return .backoff.BackoffDelay(, )
}
func ( *Standard) (context.Context) (func(error) error, error) {
return .GetInitialToken(), nil
}
func ( *Standard) () func(error) error {
return releaseToken(.noRetryIncrement).release
}
func ( *Standard) () error {
return .options.RateLimiter.AddTokens(.options.NoRetryIncrement)
}
func ( *Standard) ( context.Context, error) (func(error) error, error) {
:= .options.RetryCost
if .timeout.IsErrorTimeout().Bool() {
= .options.RetryTimeoutCost
}
, := .options.RateLimiter.GetToken(, )
if != nil {
return nil, fmt.Errorf("failed to get rate limit token, %w", )
}
return releaseToken().release, nil
}
func (error) error { return nil }
type releaseToken func() error
func ( releaseToken) ( error) error {
if != nil {
return nil
}
return ()
}