-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ported Cortex e2e modules, so we can reuse it smaller projects too. (#5)
* Ported Cortex e2e modules, so we can reuse it smaller projects too. Signed-off-by: Bartlomiej Plotka <[email protected]> * Fix. Signed-off-by: Bartlomiej Plotka <[email protected]>
- Loading branch information
Showing
21 changed files
with
3,358 additions
and
96 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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// Copyright (c) The EfficientGo Authors. | ||
// Licensed under the Apache License 2.0. | ||
|
||
package backoff | ||
|
||
// Copied from https://github.com/cortexproject/cortex/blob/0ec7b9664a01d538f1f49580b4c359a5c3cc755a/pkg/util/backoff.go | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"math/rand" | ||
"time" | ||
) | ||
|
||
// Config configures a Backoff. | ||
type Config struct { | ||
Min time.Duration `yaml:"min_period"` // Start backoff at this level | ||
Max time.Duration `yaml:"max_period"` // Increase exponentially to this level | ||
MaxRetries int `yaml:"max_retries"` // Give up after this many; zero means infinite retries | ||
} | ||
|
||
// Backoff implements exponential backoff with randomized wait times. | ||
type Backoff struct { | ||
cfg Config | ||
ctx context.Context | ||
numRetries int | ||
nextDelayMin time.Duration | ||
nextDelayMax time.Duration | ||
} | ||
|
||
// New creates a Backoff object. Pass a Context that can also terminate the operation. | ||
func New(ctx context.Context, cfg Config) *Backoff { | ||
return &Backoff{ | ||
cfg: cfg, | ||
ctx: ctx, | ||
nextDelayMin: cfg.Min, | ||
nextDelayMax: doubleDuration(cfg.Min, cfg.Max), | ||
} | ||
} | ||
|
||
// Reset the Backoff back to its initial condition. | ||
func (b *Backoff) Reset() { | ||
b.numRetries = 0 | ||
b.nextDelayMin = b.cfg.Min | ||
b.nextDelayMax = doubleDuration(b.cfg.Min, b.cfg.Max) | ||
} | ||
|
||
// Ongoing returns true if caller should keep going. | ||
func (b *Backoff) Ongoing() bool { | ||
// Stop if Context has errored or max retry count is exceeded. | ||
return b.ctx.Err() == nil && (b.cfg.MaxRetries == 0 || b.numRetries < b.cfg.MaxRetries) | ||
} | ||
|
||
// Err returns the reason for terminating the backoff, or nil if it didn't terminate. | ||
func (b *Backoff) Err() error { | ||
if b.ctx.Err() != nil { | ||
return b.ctx.Err() | ||
} | ||
if b.cfg.MaxRetries != 0 && b.numRetries >= b.cfg.MaxRetries { | ||
return fmt.Errorf("terminated after %d retries", b.numRetries) | ||
} | ||
return nil | ||
} | ||
|
||
// NumRetries returns the number of retries so far. | ||
func (b *Backoff) NumRetries() int { | ||
return b.numRetries | ||
} | ||
|
||
// Wait sleeps for the backoff time then increases the retry count and backoff time. | ||
// Returns immediately if Context is terminated. | ||
func (b *Backoff) Wait() { | ||
// Increase the number of retries and get the next delay. | ||
sleepTime := b.NextDelay() | ||
|
||
if b.Ongoing() { | ||
select { | ||
case <-b.ctx.Done(): | ||
case <-time.After(sleepTime): | ||
} | ||
} | ||
} | ||
|
||
func (b *Backoff) NextDelay() time.Duration { | ||
b.numRetries++ | ||
|
||
// Handle the edge case the min and max have the same value | ||
// (or due to some misconfig max is < min). | ||
if b.nextDelayMin >= b.nextDelayMax { | ||
return b.nextDelayMin | ||
} | ||
|
||
// Add a jitter within the next exponential backoff range. | ||
sleepTime := b.nextDelayMin + time.Duration(rand.Int63n(int64(b.nextDelayMax-b.nextDelayMin))) | ||
|
||
// Apply the exponential backoff to calculate the next jitter | ||
// range, unless we've already reached the max. | ||
if b.nextDelayMax < b.cfg.Max { | ||
b.nextDelayMin = doubleDuration(b.nextDelayMin, b.cfg.Max) | ||
b.nextDelayMax = doubleDuration(b.nextDelayMax, b.cfg.Max) | ||
} | ||
|
||
return sleepTime | ||
} | ||
|
||
func doubleDuration(value time.Duration, max time.Duration) time.Duration { | ||
value = value * 2 | ||
if value <= max { | ||
return value | ||
} | ||
return max | ||
} |
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 @@ | ||
// Copyright (c) The EfficientGo Authors. | ||
// Licensed under the Apache License 2.0. | ||
|
||
package backoff | ||
|
||
// Copied from https://github.com/cortexproject/cortex/blob/0ec7b9664a01d538f1f49580b4c359a5c3cc755a/pkg/util/backoff.go | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestBackoff_NextDelay(t *testing.T) { | ||
t.Parallel() | ||
|
||
tests := map[string]struct { | ||
minBackoff time.Duration | ||
maxBackoff time.Duration | ||
expectedRanges [][]time.Duration | ||
}{ | ||
"exponential backoff with jitter honoring min and max": { | ||
minBackoff: 100 * time.Millisecond, | ||
maxBackoff: 10 * time.Second, | ||
expectedRanges: [][]time.Duration{ | ||
{100 * time.Millisecond, 200 * time.Millisecond}, | ||
{200 * time.Millisecond, 400 * time.Millisecond}, | ||
{400 * time.Millisecond, 800 * time.Millisecond}, | ||
{800 * time.Millisecond, 1600 * time.Millisecond}, | ||
{1600 * time.Millisecond, 3200 * time.Millisecond}, | ||
{3200 * time.Millisecond, 6400 * time.Millisecond}, | ||
{6400 * time.Millisecond, 10000 * time.Millisecond}, | ||
{6400 * time.Millisecond, 10000 * time.Millisecond}, | ||
}, | ||
}, | ||
"exponential backoff with max equal to the end of a range": { | ||
minBackoff: 100 * time.Millisecond, | ||
maxBackoff: 800 * time.Millisecond, | ||
expectedRanges: [][]time.Duration{ | ||
{100 * time.Millisecond, 200 * time.Millisecond}, | ||
{200 * time.Millisecond, 400 * time.Millisecond}, | ||
{400 * time.Millisecond, 800 * time.Millisecond}, | ||
{400 * time.Millisecond, 800 * time.Millisecond}, | ||
}, | ||
}, | ||
"exponential backoff with max equal to the end of a range + 1": { | ||
minBackoff: 100 * time.Millisecond, | ||
maxBackoff: 801 * time.Millisecond, | ||
expectedRanges: [][]time.Duration{ | ||
{100 * time.Millisecond, 200 * time.Millisecond}, | ||
{200 * time.Millisecond, 400 * time.Millisecond}, | ||
{400 * time.Millisecond, 800 * time.Millisecond}, | ||
{800 * time.Millisecond, 801 * time.Millisecond}, | ||
{800 * time.Millisecond, 801 * time.Millisecond}, | ||
}, | ||
}, | ||
"exponential backoff with max equal to the end of a range - 1": { | ||
minBackoff: 100 * time.Millisecond, | ||
maxBackoff: 799 * time.Millisecond, | ||
expectedRanges: [][]time.Duration{ | ||
{100 * time.Millisecond, 200 * time.Millisecond}, | ||
{200 * time.Millisecond, 400 * time.Millisecond}, | ||
{400 * time.Millisecond, 799 * time.Millisecond}, | ||
{400 * time.Millisecond, 799 * time.Millisecond}, | ||
}, | ||
}, | ||
"min backoff is equal to max": { | ||
minBackoff: 100 * time.Millisecond, | ||
maxBackoff: 100 * time.Millisecond, | ||
expectedRanges: [][]time.Duration{ | ||
{100 * time.Millisecond, 100 * time.Millisecond}, | ||
{100 * time.Millisecond, 100 * time.Millisecond}, | ||
{100 * time.Millisecond, 100 * time.Millisecond}, | ||
}, | ||
}, | ||
"min backoff is greater then max": { | ||
minBackoff: 200 * time.Millisecond, | ||
maxBackoff: 100 * time.Millisecond, | ||
expectedRanges: [][]time.Duration{ | ||
{200 * time.Millisecond, 200 * time.Millisecond}, | ||
{200 * time.Millisecond, 200 * time.Millisecond}, | ||
{200 * time.Millisecond, 200 * time.Millisecond}, | ||
}, | ||
}, | ||
} | ||
|
||
for testName, testData := range tests { | ||
testData := testData | ||
|
||
t.Run(testName, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
b := New(context.Background(), Config{ | ||
Min: testData.minBackoff, | ||
Max: testData.maxBackoff, | ||
MaxRetries: len(testData.expectedRanges), | ||
}) | ||
|
||
for _, expectedRange := range testData.expectedRanges { | ||
delay := b.NextDelay() | ||
|
||
if delay < expectedRange[0] || delay > expectedRange[1] { | ||
t.Errorf("%d expected to be within %d and %d", delay, expectedRange[0], expectedRange[1]) | ||
} | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.