Commit 4ff0a623 authored by protolambda's avatar protolambda

op-service: fix exponential backoff strategy in case of high number of attempts

parent 8a3c8964
...@@ -14,7 +14,7 @@ type Strategy interface { ...@@ -14,7 +14,7 @@ type Strategy interface {
} }
// ExponentialStrategy performs exponential backoff. The exponential backoff // ExponentialStrategy performs exponential backoff. The exponential backoff
// function is min(e.Min + (2^attempt * 1000) + randBetween(0, e.MaxJitter), e.Max) // function is min(e.Min + (2^attempt * second), e.Max) + randBetween(0, e.MaxJitter)
type ExponentialStrategy struct { type ExponentialStrategy struct {
// Min is the minimum amount of time to wait between attempts. // Min is the minimum amount of time to wait between attempts.
Min time.Duration Min time.Duration
...@@ -23,15 +23,24 @@ type ExponentialStrategy struct { ...@@ -23,15 +23,24 @@ type ExponentialStrategy struct {
Max time.Duration Max time.Duration
// MaxJitter is the maximum amount of random jitter to insert between attempts. // MaxJitter is the maximum amount of random jitter to insert between attempts.
// Jitter is added on top of the maximum, if the maximum is reached.
MaxJitter time.Duration MaxJitter time.Duration
} }
func (e *ExponentialStrategy) Duration(attempt int) time.Duration { func (e *ExponentialStrategy) Duration(attempt int) time.Duration {
var jitter time.Duration var jitter time.Duration // non-negative jitter
if e.MaxJitter > 0 { if e.MaxJitter > 0 {
jitter = time.Duration(rand.Int63n(e.MaxJitter.Nanoseconds())) jitter = time.Duration(rand.Int63n(e.MaxJitter.Nanoseconds()))
} }
dur := e.Min + time.Duration(int(math.Pow(2, float64(attempt))*1000))*time.Millisecond if attempt < 0 {
return e.Min + jitter
}
durFloat := float64(e.Min) * float64(time.Nanosecond)
durFloat += math.Pow(2, float64(attempt)) * float64(time.Second)
dur := time.Duration(durFloat)
if durFloat > float64(e.Max) {
dur = e.Max
}
dur += jitter dur += jitter
if dur > e.Max { if dur > e.Max {
return e.Max return e.Max
...@@ -42,8 +51,9 @@ func (e *ExponentialStrategy) Duration(attempt int) time.Duration { ...@@ -42,8 +51,9 @@ func (e *ExponentialStrategy) Duration(attempt int) time.Duration {
func Exponential() Strategy { func Exponential() Strategy {
return &ExponentialStrategy{ return &ExponentialStrategy{
Max: time.Duration(10000 * time.Millisecond), Min: 0,
MaxJitter: time.Duration(250 * time.Millisecond), Max: 10 * time.Second,
MaxJitter: 250 * time.Millisecond,
} }
} }
......
package retry package retry
import ( import (
"math"
"testing" "testing"
"time" "time"
...@@ -9,13 +10,17 @@ import ( ...@@ -9,13 +10,17 @@ import (
func TestExponential(t *testing.T) { func TestExponential(t *testing.T) {
strategy := &ExponentialStrategy{ strategy := &ExponentialStrategy{
Min: 3000 * time.Millisecond, Min: 3 * time.Second,
Max: 10000 * time.Millisecond, Max: 10 * time.Second,
MaxJitter: 0, MaxJitter: 0,
} }
require.Equal(t, 3*time.Second, strategy.Duration(-1))
durations := []time.Duration{4, 5, 7, 10, 10} durations := []time.Duration{4, 5, 7, 10, 10}
for i, dur := range durations { for i, dur := range durations {
require.Equal(t, dur*time.Second, strategy.Duration(i)) require.Equal(t, dur*time.Second, strategy.Duration(i), "attempt %d", i)
} }
require.Equal(t, 10*time.Second, strategy.Duration(100))
require.Equal(t, 10*time.Second, strategy.Duration(16000))
require.Equal(t, 10*time.Second, strategy.Duration(math.MaxInt))
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment