-
Notifications
You must be signed in to change notification settings - Fork 973
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(das): add backoff to retry jobs (#2103)
## Overview Resolves #2027
- Loading branch information
Showing
4 changed files
with
222 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package das | ||
|
||
import ( | ||
"time" | ||
) | ||
|
||
var ( | ||
// first retry attempt should happen after defaultBackoffInitialInterval | ||
defaultBackoffInitialInterval = time.Minute | ||
// next retry attempt will happen with delay of previous one multiplied by defaultBackoffMultiplier | ||
defaultBackoffMultiplier = 4 | ||
// after defaultBackoffMaxRetryCount amount of attempts retry backoff interval will stop growing | ||
// and each retry attempt will produce WARN log | ||
defaultBackoffMaxRetryCount = 4 | ||
) | ||
|
||
// retryStrategy defines a backoff for retries. | ||
type retryStrategy struct { | ||
// attempts delays will follow durations stored in retryIntervals | ||
retryIntervals []time.Duration | ||
} | ||
|
||
// newRetryStrategy creates and initializes a new retry backoff. | ||
func newRetryStrategy(retryIntervals []time.Duration) retryStrategy { | ||
return retryStrategy{retryIntervals: retryIntervals} | ||
} | ||
|
||
// nextRetry creates a retry attempt with a backoff delay based on the retry backoff. | ||
// It takes the number of retry attempts and the time of the last attempt as inputs and returns a | ||
// retry instance and a boolean value indicating whether the retries amount have exceeded. | ||
func (s retryStrategy) nextRetry(lastRetry retryAttempt, lastAttempt time.Time, | ||
) (retry retryAttempt, retriesExceeded bool) { | ||
lastRetry.count++ | ||
|
||
if len(s.retryIntervals) == 0 { | ||
return lastRetry, false | ||
} | ||
|
||
if lastRetry.count > len(s.retryIntervals) { | ||
// try count exceeded backoff try limit | ||
lastRetry.after = lastAttempt.Add(s.retryIntervals[len(s.retryIntervals)-1]) | ||
return lastRetry, true | ||
} | ||
|
||
lastRetry.after = lastAttempt.Add(s.retryIntervals[lastRetry.count-1]) | ||
return lastRetry, false | ||
} | ||
|
||
// exponentialBackoff generates an array of time.Duration values using an exponential growth | ||
// multiplier. | ||
func exponentialBackoff(baseInterval time.Duration, multiplier, amount int) []time.Duration { | ||
backoff := make([]time.Duration, 0, amount) | ||
next := baseInterval | ||
for i := 0; i < amount; i++ { | ||
backoff = append(backoff, next) | ||
next *= time.Duration(multiplier) | ||
} | ||
return backoff | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package das | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func Test_exponentialBackoff(t *testing.T) { | ||
type args struct { | ||
baseInterval time.Duration | ||
factor int | ||
amount int | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want []time.Duration | ||
}{ | ||
{ | ||
name: "defaults", | ||
args: args{ | ||
baseInterval: time.Minute, | ||
factor: 4, | ||
amount: 4, | ||
}, | ||
want: []time.Duration{ | ||
time.Minute, | ||
4 * time.Minute, | ||
16 * time.Minute, | ||
64 * time.Minute, | ||
}}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
assert.Equalf(t, | ||
tt.want, exponentialBackoff(tt.args.baseInterval, tt.args.factor, tt.args.amount), | ||
"exponentialBackoff(%v, %v, %v)", tt.args.baseInterval, tt.args.factor, tt.args.amount) | ||
}) | ||
} | ||
} | ||
|
||
func Test_retryStrategy_nextRetry(t *testing.T) { | ||
tNow := time.Now() | ||
type args struct { | ||
retry retryAttempt | ||
lastAttempt time.Time | ||
} | ||
tests := []struct { | ||
name string | ||
backoff retryStrategy | ||
args args | ||
wantRetry retryAttempt | ||
wantRetriesExceeded bool | ||
}{ | ||
{ | ||
name: "empty_strategy", | ||
backoff: newRetryStrategy(nil), | ||
args: args{ | ||
retry: retryAttempt{count: 1}, | ||
lastAttempt: tNow, | ||
}, | ||
wantRetry: retryAttempt{ | ||
count: 2, | ||
}, | ||
wantRetriesExceeded: false, | ||
}, | ||
{ | ||
name: "before_limit", | ||
backoff: newRetryStrategy([]time.Duration{time.Second, time.Minute}), | ||
args: args{ | ||
retry: retryAttempt{count: 1}, | ||
lastAttempt: tNow, | ||
}, | ||
wantRetry: retryAttempt{ | ||
count: 2, | ||
after: tNow.Add(time.Minute), | ||
}, | ||
wantRetriesExceeded: false, | ||
}, | ||
{ | ||
name: "after_limit", | ||
backoff: newRetryStrategy([]time.Duration{time.Second, time.Minute}), | ||
args: args{ | ||
retry: retryAttempt{count: 2}, | ||
lastAttempt: tNow, | ||
}, | ||
wantRetry: retryAttempt{ | ||
count: 3, | ||
after: tNow.Add(time.Minute), | ||
}, | ||
wantRetriesExceeded: true, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
s := retryStrategy{ | ||
retryIntervals: tt.backoff.retryIntervals, | ||
} | ||
gotRetry, gotRetriesExceeded := s.nextRetry(tt.args.retry, tt.args.lastAttempt) | ||
assert.Equalf(t, tt.wantRetry, gotRetry, | ||
"nextRetry(%v, %v)", tt.args.retry, tt.args.lastAttempt) | ||
assert.Equalf(t, tt.wantRetriesExceeded, gotRetriesExceeded, | ||
"nextRetry(%v, %v)", tt.args.retry, tt.args.lastAttempt) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters