Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions backoff.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ package durex
import (
"math"
"math/rand"
"sync"
"time"
)

var (
jitterRandMu sync.Mutex
jitterRand = rand.New(rand.NewSource(time.Now().UnixNano()))
)

// BackoffStrategy calculates the delay before retrying a failed command.
type BackoffStrategy interface {
// NextDelay returns the delay before the next retry attempt.
Expand Down Expand Up @@ -72,14 +78,31 @@ type JitteredBackoff struct {

// NextDelay implements BackoffStrategy.
func (b JitteredBackoff) NextDelay(attempt int) time.Duration {
if b.Strategy == nil {
return 0
}

base := b.Strategy.NextDelay(attempt)
if b.JitterRate <= 0 {
return base
}

jitterRange := float64(base) * b.JitterRate
jitter := (rand.Float64()*2 - 1) * jitterRange // -jitterRange to +jitterRange
return time.Duration(float64(base) + jitter)
jitterRate := b.JitterRate
if jitterRate > 1 {
jitterRate = 1
}

jitterRange := float64(base) * jitterRate
jitterRandMu.Lock()
randomValue := jitterRand.Float64()
jitterRandMu.Unlock()
jitter := (randomValue*2 - 1) * jitterRange // -jitterRange to +jitterRange
delay := time.Duration(float64(base) + jitter)
if delay < 0 {
return 0
}

return delay
}

// DefaultExponentialBackoff returns a sensible default exponential backoff.
Expand Down
24 changes: 24 additions & 0 deletions backoff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,30 @@ func TestJitteredBackoff(t *testing.T) {
}
}

func TestJitteredBackoff_NilStrategy(t *testing.T) {
backoff := durex.JitteredBackoff{JitterRate: 0.5}

delay := backoff.NextDelay(1)
if delay != 0 {
t.Errorf("Expected 0 with nil strategy, got %v", delay)
}
}

func TestJitteredBackoff_ClampsNegativeDelay(t *testing.T) {
base := durex.ConstantBackoff{Delay: 100 * time.Millisecond}
backoff := durex.JitteredBackoff{
Strategy: base,
JitterRate: 10.0,
}

for i := 0; i < 100; i++ {
delay := backoff.NextDelay(1)
if delay < 0 {
t.Fatalf("Expected non-negative delay, got %v", delay)
}
}
}

func TestJitteredBackoff_ZeroJitter(t *testing.T) {
base := durex.ConstantBackoff{Delay: 100 * time.Millisecond}
backoff := durex.JitteredBackoff{
Expand Down