Skip to content

Commit

Permalink
Use math/rand global random source and deprecate PRNG global variable
Browse files Browse the repository at this point in the history
  • Loading branch information
justinrixx committed Mar 2, 2024
1 parent 8f425fd commit ea41c06
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 5 deletions.
24 changes: 19 additions & 5 deletions rehttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ import (
// PRNG is the *math.Rand value to use to add jitter to the backoff
// algorithm used in ExpJitterDelay. By default it uses a *rand.Rand
// initialized with a source based on the current time in nanoseconds.
//
// Deprecated: math/rand sources can panic if used concurrently without
// synchronization. PRNG is no longer used by this package and its use
// outside this package is discouraged.
// https://github.com/PuerkitoBio/rehttp/issues/12
var PRNG = rand.New(rand.NewSource(time.Now().UnixNano()))

// terribly named interface to detect errors that support Temporary.
Expand Down Expand Up @@ -253,18 +258,27 @@ func ConstDelay(delay time.Duration) DelayFn {
}
}

// ExpJitterDelay returns a DelayFn that returns a delay between 0 and
// base * 2^attempt capped at max (an exponential backoff delay with
// jitter).
// ExpJitterDelay is identical to [ExpJitterDelayWithRand], using
// math/rand.Int63n as the random generator function.
// This package does not call [rand.Seed], so it is the caller's
// responsibility to ensure the default generator is properly seeded.
func ExpJitterDelay(base, max time.Duration) DelayFn {
return ExpJitterDelayWithRand(base, max, rand.Int63n)
}

// ExpJitterDelayWithRand returns a DelayFn that returns a delay
// between 0 and base * 2^attempt capped at max (an exponential
// backoff delay with jitter). The generator argument is expected
// to generate a random int64 in the half open interval [0, n).
//
// See the full jitter algorithm in:
// http://www.awsarchitectureblog.com/2015/03/backoff.html
func ExpJitterDelay(base, max time.Duration) DelayFn {
func ExpJitterDelayWithRand(base, max time.Duration, generator func(n int64) int64) DelayFn {
return func(attempt Attempt) time.Duration {
exp := math.Pow(2, float64(attempt.Index))
top := float64(base) * exp
return time.Duration(
PRNG.Int63n(int64(math.Min(float64(max), top))),
generator(int64(math.Min(float64(max), top))),
)
}
}
Expand Down
11 changes: 11 additions & 0 deletions rehttp_delayfn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,14 @@ func TestExpJitterDelay(t *testing.T) {
assert.True(t, delay <= actual, "%d: %s > %s", i, delay, actual)
}
}

func TestExpJitterDelayWithRand(t *testing.T) {
fn := ExpJitterDelayWithRand(time.Second, 5*time.Second, func(n int64) int64 { return 999_999_999 % n })
for i := 0; i < 10; i++ {
delay := fn(Attempt{Index: i})
top := math.Pow(2, float64(i)) * float64(time.Second)
actual := time.Duration(math.Min(float64(5*time.Second), top))
assert.True(t, delay > 0, "%d: %s <= 0", i, delay)
assert.True(t, delay <= actual, "%d: %s > %s", i, delay, actual)
}
}

0 comments on commit ea41c06

Please sign in to comment.