From 680ac11ef97e2ed04b2f6b5e387086466ae81676 Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Sun, 27 Mar 2022 00:16:22 +0300 Subject: [PATCH] client: determenistic retry backoff Signed-off-by: Vasiliy Tolstov --- client/backoff.go | 17 ++++++++++++++++- client/backoff_test.go | 2 +- client/client.go | 4 ++-- client/retry.go | 20 +++++++++++++++++--- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/client/backoff.go b/client/backoff.go index 0aed7c60..d548b724 100644 --- a/client/backoff.go +++ b/client/backoff.go @@ -2,6 +2,7 @@ package client import ( "context" + "math" "time" "go.unistack.org/micro/v3/util/backoff" @@ -10,6 +11,20 @@ import ( // BackoffFunc is the backoff call func type BackoffFunc func(ctx context.Context, req Request, attempts int) (time.Duration, error) -func exponentialBackoff(ctx context.Context, req Request, attempts int) (time.Duration, error) { +// BackoffExp using exponential backoff func +func BackoffExp(_ context.Context, _ Request, attempts int) (time.Duration, error) { return backoff.Do(attempts), nil } + +// BackoffInterval specifies randomization interval for backoff func +func BackoffInterval(min time.Duration, max time.Duration) BackoffFunc { + return func(_ context.Context, _ Request, attempts int) (time.Duration, error) { + td := time.Duration(time.Duration(math.Pow(float64(attempts), math.E)) * time.Millisecond * 100) + if td < min { + return min, nil + } else if td > max { + return max, nil + } + return td, nil + } +} diff --git a/client/backoff_test.go b/client/backoff_test.go index 5b9b60b8..2ab854f7 100644 --- a/client/backoff_test.go +++ b/client/backoff_test.go @@ -22,7 +22,7 @@ func TestBackoff(t *testing.T) { } for i := 0; i < 5; i++ { - d, err := exponentialBackoff(context.TODO(), r, i) + d, err := BackoffExp(context.TODO(), r, i) if err != nil { t.Fatal(err) } diff --git a/client/client.go b/client/client.go index 63cb4f85..71bedfd9 100644 --- a/client/client.go +++ b/client/client.go @@ -14,8 +14,8 @@ var ( DefaultClient Client = NewClient() // DefaultContentType is the default content-type if not specified DefaultContentType = "application/json" - // DefaultBackoff is the default backoff function for retries - DefaultBackoff = exponentialBackoff + // DefaultBackoff is the default backoff function for retries (minimum 10 millisecond and maximum 5 second) + DefaultBackoff = BackoffInterval(10*time.Millisecond, 5*time.Second) // DefaultRetry is the default check-for-retry function for retries DefaultRetry = RetryNever // DefaultRetries is the default number of times a request is tried diff --git a/client/retry.go b/client/retry.go index 40272fd2..a218b3fe 100644 --- a/client/retry.go +++ b/client/retry.go @@ -19,18 +19,32 @@ func RetryNever(ctx context.Context, req Request, retryCount int, err error) (bo return false, nil } -// RetryOnError retries a request on a 500 or timeout error +// RetryOnError retries a request on a 500 or 408 (timeout) error func RetryOnError(_ context.Context, _ Request, _ int, err error) (bool, error) { if err == nil { return false, nil } - me := errors.FromError(err) switch me.Code { // retry on timeout or internal server error case 408, 500: return true, nil } - return false, nil } + +// RetryOnErrors retries a request on specified error codes +func RetryOnErrors(codes ...int32) RetryFunc { + return func(_ context.Context, _ Request, _ int, err error) (bool, error) { + if err == nil { + return false, nil + } + me := errors.FromError(err) + for _, code := range codes { + if me.Code == code { + return true, nil + } + } + return false, nil + } +}