125 lines
3.3 KiB
Go
125 lines
3.3 KiB
Go
package retry
|
|
|
|
import (
|
|
"time"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
)
|
|
|
|
// WithDefaultInterceptor returns interceptor that DOESN'T retry anything.
|
|
// Its possible to change its behaviour with call options.
|
|
func WithDefaultInterceptor() grpc.DialOption {
|
|
return grpc.WithUnaryInterceptor(Interceptor())
|
|
}
|
|
|
|
// WithMax option sets quantity of retry attempts.
|
|
// It handles negative maxRetryCount as INFINITE retries.
|
|
func WithMax(maxRetryCount int) grpc.CallOption {
|
|
return newOption(setRetryQuantity(maxRetryCount))
|
|
}
|
|
|
|
// WithCodes overrides the whole retriable codes list.
|
|
func WithCodes(codes ...codes.Code) grpc.CallOption {
|
|
return newOption(setRetriableCodes(codes...))
|
|
}
|
|
|
|
// WithAttemptHeader adds retry attempt number to context outgoing metadata, with key "x-retry-attempt".
|
|
func WithAttemptHeader(enable bool) grpc.CallOption {
|
|
return newOption(setAttemptHeader(enable))
|
|
}
|
|
|
|
// WithPerCallTimeout adds timeout for retry calls.
|
|
func WithPerCallTimeout(to time.Duration) grpc.CallOption {
|
|
return newOption(setPerCallTimeout(to))
|
|
}
|
|
|
|
// WithBackoff sets up interceptor with custom defined backoff function
|
|
func WithBackoff(f BackoffFunc) grpc.CallOption {
|
|
return newOption(setBackoff(f))
|
|
}
|
|
|
|
// DefaultBackoff uses exponential backoff with jitter, with base = 50ms, and maximum timeout = 1 minute.
|
|
func DefaultBackoff() BackoffFunc {
|
|
return DefaultExponentialJitterBackoff()
|
|
}
|
|
|
|
// WithDefaultExponentialJitterBackoff same as WithDefaultBackoff
|
|
func DefaultExponentialJitterBackoff() BackoffFunc {
|
|
return BackoffExponentialWithJitter(defaultExponentialBackoffBase, defaultExponentialBackoffCap)
|
|
}
|
|
|
|
// DefaultLinearJitterBackoff uses linear backoff with base = 50ms, and jitter = +-10%
|
|
func DefaultLinearJitterBackoff() BackoffFunc {
|
|
return BackoffLinearWithJitter(defaultLinearBackoffTimeout, defaultLinearBackoffJitter)
|
|
}
|
|
|
|
type options struct {
|
|
maxRetryCount int
|
|
retriableCodes []codes.Code
|
|
addCountToHeader bool
|
|
perCallTimeout time.Duration
|
|
backoffFunc BackoffFunc
|
|
}
|
|
|
|
var (
|
|
// TODO(seukyaso): Consider adding some non-zero default retry options
|
|
DefaultRetriableCodes = []codes.Code{codes.ResourceExhausted, codes.Unavailable}
|
|
|
|
defaultOptions = &options{
|
|
maxRetryCount: 0,
|
|
retriableCodes: DefaultRetriableCodes,
|
|
addCountToHeader: false,
|
|
perCallTimeout: 0,
|
|
backoffFunc: nil,
|
|
}
|
|
)
|
|
|
|
const (
|
|
defaultLinearBackoffTimeout = 50 * time.Millisecond
|
|
defaultLinearBackoffJitter = 0.1
|
|
defaultExponentialBackoffBase = 50 * time.Millisecond
|
|
defaultExponentialBackoffCap = 1 * time.Minute
|
|
)
|
|
|
|
type applyOptionFunc func(opt *options)
|
|
|
|
type interceptorOption struct {
|
|
grpc.EmptyCallOption
|
|
applyFunc applyOptionFunc
|
|
}
|
|
|
|
func newOption(f applyOptionFunc) grpc.CallOption {
|
|
return interceptorOption{applyFunc: f}
|
|
}
|
|
|
|
func setRetryQuantity(r int) applyOptionFunc {
|
|
return func(opt *options) {
|
|
opt.maxRetryCount = r
|
|
}
|
|
}
|
|
|
|
func setRetriableCodes(codes ...codes.Code) applyOptionFunc {
|
|
return func(opt *options) {
|
|
opt.retriableCodes = codes
|
|
}
|
|
}
|
|
|
|
func setAttemptHeader(enable bool) applyOptionFunc {
|
|
return func(opt *options) {
|
|
opt.addCountToHeader = enable
|
|
}
|
|
}
|
|
|
|
func setPerCallTimeout(to time.Duration) applyOptionFunc {
|
|
return func(opt *options) {
|
|
opt.perCallTimeout = to
|
|
}
|
|
}
|
|
|
|
func setBackoff(f BackoffFunc) applyOptionFunc {
|
|
return func(opt *options) {
|
|
opt.backoffFunc = f
|
|
}
|
|
}
|