18
									
								
								client/backoff.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								client/backoff.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| package client | ||||
|  | ||||
| import ( | ||||
| 	"math" | ||||
| 	"time" | ||||
|  | ||||
| 	"golang.org/x/net/context" | ||||
| ) | ||||
|  | ||||
| type BackoffFunc func(ctx context.Context, req Request, attempts int) (time.Duration, error) | ||||
|  | ||||
| // exponential backoff | ||||
| func exponentialBackoff(ctx context.Context, req Request, attempts int) (time.Duration, error) { | ||||
| 	if attempts == 0 { | ||||
| 		return time.Duration(0), nil | ||||
| 	} | ||||
| 	return time.Duration(math.Pow(10, float64(attempts))) * time.Millisecond, nil | ||||
| } | ||||
							
								
								
									
										26
									
								
								client/backoff_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								client/backoff_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| package client | ||||
|  | ||||
| import ( | ||||
| 	"math" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"golang.org/x/net/context" | ||||
| ) | ||||
|  | ||||
| func TestBackoff(t *testing.T) { | ||||
| 	delta := time.Duration(0) | ||||
|  | ||||
| 	for i := 0; i < 5; i++ { | ||||
| 		d, err := exponentialBackoff(context.TODO(), NewJsonRequest("test", "test", nil), i) | ||||
| 		if err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		if d < delta { | ||||
| 			t.Fatalf("Expected greater than %v, got %v", delta, d) | ||||
| 		} | ||||
|  | ||||
| 		delta = time.Millisecond * time.Duration(math.Pow(10, float64(i+1))) | ||||
| 	} | ||||
| } | ||||
| @@ -77,6 +77,7 @@ type RequestOption func(*RequestOptions) | ||||
| var ( | ||||
| 	DefaultClient Client = newRpcClient() | ||||
|  | ||||
| 	DefaultBackoff        = exponentialBackoff | ||||
| 	DefaultRetries        = 1 | ||||
| 	DefaultRequestTimeout = time.Second * 5 | ||||
| ) | ||||
|   | ||||
| @@ -37,6 +37,8 @@ type Options struct { | ||||
| type CallOptions struct { | ||||
| 	SelectOptions []selector.SelectOption | ||||
|  | ||||
| 	// Backoff func | ||||
| 	Backoff BackoffFunc | ||||
| 	// Transport Dial Timeout | ||||
| 	DialTimeout time.Duration | ||||
| 	// Number of Call attempts | ||||
| @@ -67,6 +69,7 @@ func newOptions(options ...Option) Options { | ||||
| 	opts := Options{ | ||||
| 		Codecs: make(map[string]codec.NewCodec), | ||||
| 		CallOptions: CallOptions{ | ||||
| 			Backoff:        DefaultBackoff, | ||||
| 			Retries:        DefaultRetries, | ||||
| 			RequestTimeout: DefaultRequestTimeout, | ||||
| 			DialTimeout:    transport.DefaultDialTimeout, | ||||
| @@ -151,6 +154,14 @@ func Wrap(w Wrapper) Option { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Backoff is used to set the backoff function used | ||||
| // when retrying Calls | ||||
| func Backoff(fn BackoffFunc) Option { | ||||
| 	return func(o *Options) { | ||||
| 		o.CallOptions.Backoff = fn | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Number of retries when making the request. | ||||
| // Should this be a Call Option? | ||||
| func Retries(i int) Option { | ||||
| @@ -182,6 +193,14 @@ func WithSelectOption(so selector.SelectOption) CallOption { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithBackoff is a CallOption which overrides that which | ||||
| // set in Options.CallOptions | ||||
| func WithBackoff(fn BackoffFunc) CallOption { | ||||
| 	return func(o *CallOptions) { | ||||
| 		o.Backoff = fn | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithRetries is a CallOption which overrides that which | ||||
| // set in Options.CallOptions | ||||
| func WithRetries(i int) CallOption { | ||||
|   | ||||
| @@ -203,6 +203,17 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac | ||||
| 	var grr error | ||||
|  | ||||
| 	for i := 0; i < callOpts.Retries; i++ { | ||||
| 		// call backoff first. Someone may want an initial start delay | ||||
| 		t, err := callOpts.Backoff(ctx, request, i) | ||||
| 		if err != nil { | ||||
| 			return errors.InternalServerError("go.micro.client", err.Error()) | ||||
| 		} | ||||
|  | ||||
| 		// only sleep if greater than 0 | ||||
| 		if t.Seconds() > 0 { | ||||
| 			time.Sleep(t) | ||||
| 		} | ||||
|  | ||||
| 		node, err := next() | ||||
| 		if err != nil && err == selector.ErrNotFound { | ||||
| 			return errors.NotFound("go.micro.client", err.Error()) | ||||
| @@ -257,6 +268,17 @@ func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOpt | ||||
| 	var grr error | ||||
|  | ||||
| 	for i := 0; i < callOpts.Retries; i++ { | ||||
| 		// call backoff first. Someone may want an initial start delay | ||||
| 		t, err := callOpts.Backoff(ctx, request, i) | ||||
| 		if err != nil { | ||||
| 			return nil, errors.InternalServerError("go.micro.client", err.Error()) | ||||
| 		} | ||||
|  | ||||
| 		// only sleep if greater than 0 | ||||
| 		if t.Seconds() > 0 { | ||||
| 			time.Sleep(t) | ||||
| 		} | ||||
|  | ||||
| 		node, err := next() | ||||
| 		if err != nil && err == selector.ErrNotFound { | ||||
| 			return nil, errors.NotFound("go.micro.client", err.Error()) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user