From 8eb0f9bff63727977df34ab98aaf649462af6a37 Mon Sep 17 00:00:00 2001 From: ninedraft Date: Fri, 22 Jan 2021 21:33:57 +0300 Subject: [PATCH 1/2] refactor(retry.sleepDuration): :recycle: extract jitterBackoff function --- retry.go | 49 ++++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/retry.go b/retry.go index 7500b709..07719601 100644 --- a/retry.go +++ b/retry.go @@ -133,36 +133,39 @@ func Backoff(operation func() (*Response, error), options ...Option) error { func sleepDuration(resp *Response, min, max time.Duration, attempt int) (time.Duration, error) { const maxInt = 1<<31 - 1 // max int for arch 386 - if max < 0 { max = maxInt } - if resp == nil { - goto defaultCase + return jitterBackoff(min, max, attempt), nil } - // 1. Check for custom callback - if retryAfterFunc := resp.Request.client.RetryAfter; retryAfterFunc != nil { - result, err := retryAfterFunc(resp.Request.client, resp) - if err != nil { - return 0, err // i.e. 'API quota exceeded' - } - if result == 0 { - goto defaultCase - } - if result < 0 || max < result { - result = max - } - if result < min { - result = min - } - return result, nil + retryAfterFunc := resp.Request.client.RetryAfter + + // Check for custom callback + if retryAfterFunc == nil { + return jitterBackoff(min, max, attempt), nil } - // 2. Return capped exponential backoff with jitter - // http://www.awsarchitectureblog.com/2015/03/backoff.html -defaultCase: + result, err := retryAfterFunc(resp.Request.client, resp) + if err != nil { + return 0, err // i.e. 'API quota exceeded' + } + if result == 0 { + return jitterBackoff(min, max, attempt), nil + } + if result < 0 || max < result { + result = max + } + if result < min { + result = min + } + return result, nil +} + +// Return capped exponential backoff with jitter +// http://www.awsarchitectureblog.com/2015/03/backoff.html +func jitterBackoff(min, max time.Duration, attempt int) time.Duration { base := float64(min) capLevel := float64(max) @@ -174,5 +177,5 @@ defaultCase: result = min } - return result, nil + return result } From 621a6432d43fd828fc8657a823d775b43228bde9 Mon Sep 17 00:00:00 2001 From: ninedraft Date: Fri, 22 Jan 2021 21:46:43 +0300 Subject: [PATCH 2/2] refactor(retry.sleepDuration): :recycle: use local random generator --- retry.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/retry.go b/retry.go index 07719601..a56f3b4c 100644 --- a/retry.go +++ b/retry.go @@ -8,6 +8,7 @@ import ( "context" "math" "math/rand" + "sync" "time" ) @@ -170,8 +171,8 @@ func jitterBackoff(min, max time.Duration, attempt int) time.Duration { capLevel := float64(max) temp := math.Min(capLevel, base*math.Exp2(float64(attempt))) - ri := int64(temp / 2) - result := time.Duration(math.Abs(float64(ri + rand.Int63n(ri)))) + ri := time.Duration(temp / 2) + result := randDuration(ri) if result < min { result = min @@ -179,3 +180,21 @@ func jitterBackoff(min, max time.Duration, attempt int) time.Duration { return result } + +var rnd = newRnd() +var rndMu sync.Mutex + +func randDuration(center time.Duration) time.Duration { + rndMu.Lock() + defer rndMu.Unlock() + + var ri = int64(center) + var jitter = rnd.Int63n(ri) + return time.Duration(math.Abs(float64(ri + jitter))) +} + +func newRnd() *rand.Rand { + var seed = time.Now().UnixNano() + var src = rand.NewSource(seed) + return rand.New(src) +}