1// Copyright 2016 Michal Witkowski. All Rights Reserved. 2// See LICENSE for licensing terms. 3 4package grpc_retry 5 6import ( 7 "time" 8 9 "golang.org/x/net/context" 10 "google.golang.org/grpc" 11 "google.golang.org/grpc/codes" 12) 13 14var ( 15 // DefaultRetriableCodes is a set of well known types gRPC codes that should be retri-able. 16 // 17 // `ResourceExhausted` means that the user quota, e.g. per-RPC limits, have been reached. 18 // `Unavailable` means that system is currently unavailable and the client should retry again. 19 DefaultRetriableCodes = []codes.Code{codes.ResourceExhausted, codes.Unavailable} 20 21 defaultOptions = &options{ 22 max: 0, // disabed 23 perCallTimeout: 0, // disabled 24 includeHeader: true, 25 codes: DefaultRetriableCodes, 26 backoffFunc: BackoffFuncContext(func(ctx context.Context, attempt uint) time.Duration { 27 return BackoffLinearWithJitter(50*time.Millisecond /*jitter*/, 0.10)(attempt) 28 }), 29 } 30) 31 32// BackoffFunc denotes a family of functions that control the backoff duration between call retries. 33// 34// They are called with an identifier of the attempt, and should return a time the system client should 35// hold off for. If the time returned is longer than the `context.Context.Deadline` of the request 36// the deadline of the request takes precedence and the wait will be interrupted before proceeding 37// with the next iteration. 38type BackoffFunc func(attempt uint) time.Duration 39 40// BackoffFuncContext denotes a family of functions that control the backoff duration between call retries. 41// 42// They are called with an identifier of the attempt, and should return a time the system client should 43// hold off for. If the time returned is longer than the `context.Context.Deadline` of the request 44// the deadline of the request takes precedence and the wait will be interrupted before proceeding 45// with the next iteration. The context can be used to extract request scoped metadata and context values. 46type BackoffFuncContext func(ctx context.Context, attempt uint) time.Duration 47 48// Disable disables the retry behaviour on this call, or this interceptor. 49// 50// Its semantically the same to `WithMax` 51func Disable() CallOption { 52 return WithMax(0) 53} 54 55// WithMax sets the maximum number of retries on this call, or this interceptor. 56func WithMax(maxRetries uint) CallOption { 57 return CallOption{applyFunc: func(o *options) { 58 o.max = maxRetries 59 }} 60} 61 62// WithBackoff sets the `BackoffFunc` used to control time between retries. 63func WithBackoff(bf BackoffFunc) CallOption { 64 return CallOption{applyFunc: func(o *options) { 65 o.backoffFunc = BackoffFuncContext(func(ctx context.Context, attempt uint) time.Duration { 66 return bf(attempt) 67 }) 68 }} 69} 70 71// WithBackoffContext sets the `BackoffFuncContext` used to control time between retries. 72func WithBackoffContext(bf BackoffFuncContext) CallOption { 73 return CallOption{applyFunc: func(o *options) { 74 o.backoffFunc = bf 75 }} 76} 77 78// WithCodes sets which codes should be retried. 79// 80// Please *use with care*, as you may be retrying non-idempotend calls. 81// 82// You cannot automatically retry on Cancelled and Deadline, please use `WithPerRetryTimeout` for these. 83func WithCodes(retryCodes ...codes.Code) CallOption { 84 return CallOption{applyFunc: func(o *options) { 85 o.codes = retryCodes 86 }} 87} 88 89// WithPerRetryTimeout sets the RPC timeout per call (including initial call) on this call, or this interceptor. 90// 91// The context.Deadline of the call takes precedence and sets the maximum time the whole invocation 92// will take, but WithPerRetryTimeout can be used to limit the RPC time per each call. 93// 94// For example, with context.Deadline = now + 10s, and WithPerRetryTimeout(3 * time.Seconds), each 95// of the retry calls (including the initial one) will have a deadline of now + 3s. 96// 97// A value of 0 disables the timeout overrides completely and returns to each retry call using the 98// parent `context.Deadline`. 99func WithPerRetryTimeout(timeout time.Duration) CallOption { 100 return CallOption{applyFunc: func(o *options) { 101 o.perCallTimeout = timeout 102 }} 103} 104 105type options struct { 106 max uint 107 perCallTimeout time.Duration 108 includeHeader bool 109 codes []codes.Code 110 backoffFunc BackoffFuncContext 111} 112 113// CallOption is a grpc.CallOption that is local to grpc_retry. 114type CallOption struct { 115 grpc.EmptyCallOption // make sure we implement private after() and before() fields so we don't panic. 116 applyFunc func(opt *options) 117} 118 119func reuseOrNewWithCallOptions(opt *options, callOptions []CallOption) *options { 120 if len(callOptions) == 0 { 121 return opt 122 } 123 optCopy := &options{} 124 *optCopy = *opt 125 for _, f := range callOptions { 126 f.applyFunc(optCopy) 127 } 128 return optCopy 129} 130 131func filterCallOptions(callOptions []grpc.CallOption) (grpcOptions []grpc.CallOption, retryOptions []CallOption) { 132 for _, opt := range callOptions { 133 if co, ok := opt.(CallOption); ok { 134 retryOptions = append(retryOptions, co) 135 } else { 136 grpcOptions = append(grpcOptions, opt) 137 } 138 } 139 return grpcOptions, retryOptions 140} 141