Skip to content

Commit

Permalink
backoff: remove global rand.Seed and using locked rand.Rand (sethvarg…
Browse files Browse the repository at this point in the history
  • Loading branch information
zchee committed Dec 14, 2022
1 parent 009eed1 commit 9eb639b
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 6 deletions.
10 changes: 4 additions & 6 deletions backoff.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ import (
"time"
)

func init() {
rand.Seed(time.Now().UnixNano())
}

// Backoff is an interface that backs off.
type Backoff interface {
// Next returns the time duration to wait and whether to stop.
Expand All @@ -31,13 +27,14 @@ func (b BackoffFunc) Next() (time.Duration, bool) {
// returned 20s, the value could be between 15 and 25 seconds. The value can
// never be less than 0.
func WithJitter(j time.Duration, next Backoff) Backoff {
r := &lockedSource{src: rand.New(rand.NewSource(time.Now().UnixNano()))}
return BackoffFunc(func() (time.Duration, bool) {
val, stop := next.Next()
if stop {
return 0, true
}

diff := time.Duration(rand.Int63n(int64(j)*2) - int64(j))
diff := time.Duration(r.Int63n(int64(j)*2) - int64(j))
val = val + diff
if val < 0 {
val = 0
Expand All @@ -51,14 +48,15 @@ func WithJitter(j time.Duration, next Backoff) Backoff {
// the backoff returned 20s, the value could be between 19 and 21 seconds. The
// value can never be less than 0 or greater than 100.
func WithJitterPercent(j uint64, next Backoff) Backoff {
r := &lockedSource{src: rand.New(rand.NewSource(time.Now().UnixNano()))}
return BackoffFunc(func() (time.Duration, bool) {
val, stop := next.Next()
if stop {
return 0, true
}

// Get a value between -j and j, the convert to a percentage
top := rand.Int63n(int64(j)*2) - int64(j)
top := r.Int63n(int64(j)*2) - int64(j)
pct := 1 - float64(top)/100.0

val = time.Duration(float64(val) * pct)
Expand Down
50 changes: 50 additions & 0 deletions rand.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package retry

import (
"math/rand"
"sync"
)

type lockedSource struct {
src *rand.Rand
lk sync.Mutex
}

var _ rand.Source64 = (*lockedSource)(nil)

// Int63 mimics math/rand.(*Rand).Int63 with mutex locked.
func (r *lockedSource) Int63() int64 {
r.lk.Lock()
defer r.lk.Unlock()
return r.src.Int63()
}

// Seed mimics math/rand.(*Rand).Seed with mutex locked.
func (r *lockedSource) Seed(seed int64) {
r.lk.Lock()
defer r.lk.Unlock()
r.src.Seed(seed)
}

// Uint64 mimics math/rand.(*Rand).Uint64 with mutex locked.
func (r *lockedSource) Uint64() uint64 {
r.lk.Lock()
defer r.lk.Unlock()
return r.src.Uint64()
}

// Int63n mimics math/rand.(*Rand).Int63n with mutex locked.
func (r *lockedSource) Int63n(n int64) int64 {
if n <= 0 {
panic("invalid argument to Int63n")
}
if n&(n-1) == 0 { // n is power of two, can mask
return r.Int63() & (n - 1)
}
max := int64((1 << 63) - 1 - (1<<63)%uint64(n))
v := r.Int63()
for v > max {
v = r.Int63()
}
return v % n
}

0 comments on commit 9eb639b

Please sign in to comment.