diff --git a/utils/rand.go b/utils/rand.go index 2fe5e80a..78792da9 100644 --- a/utils/rand.go +++ b/utils/rand.go @@ -9,12 +9,72 @@ package utils import ( + crand "crypto/rand" + "encoding/binary" "math/rand" + "sync" "time" ) -func init() { - rand.Seed(time.Now().UnixNano()) +// check lockedSource implements rand.Source +var _ rand.Source = (*lockedSource)(nil) +var _ rand.Source64 = (*lockedSource64)(nil) + +type lockedSource struct { + mu sync.Mutex + src rand.Source +} + +func (src *lockedSource) Int63() int64 { + src.mu.Lock() + defer src.mu.Unlock() + return src.src.Int63() +} + +func (src *lockedSource) Seed(seed int64) { + src.mu.Lock() + defer src.mu.Unlock() + src.src.Seed(seed) +} + +type lockedSource64 struct { + mu sync.Mutex + src rand.Source64 +} + +func (src *lockedSource64) Int63() int64 { + src.mu.Lock() + defer src.mu.Unlock() + return src.src.Int63() +} + +func (src *lockedSource64) Uint64() uint64 { + src.mu.Lock() + defer src.mu.Unlock() + return src.src.Uint64() +} + +func (src *lockedSource64) Seed(seed int64) { + src.mu.Lock() + defer src.mu.Unlock() + src.src.Seed(seed) +} + +func newSeed() int64 { + var seed int64 + if err := binary.Read(crand.Reader, binary.BigEndian, &seed); err != nil { + // fallback to timestamp + seed = time.Now().UnixNano() + } + return seed +} + +func newGlobalRand() *rand.Rand { + src := rand.NewSource(newSeed()) + if src64, ok := src.(rand.Source64); ok { + return rand.New(&lockedSource64{src: src64}) + } + return rand.New(&lockedSource{src: src}) } // Rand is an interface for a set of methods that return random value. @@ -25,22 +85,25 @@ type Rand interface { } // DefaultRand is an implementation of Rand interface. +// It is safe for concurrent use by multiple goroutines. type DefaultRand struct{} +var globalRand = newGlobalRand() + // Int63n returns, as an int64, a non-negative pseudo-random number in [0,n) // from the default Source. func (r *DefaultRand) Int63n(n int64) int64 { - return rand.Int63n(n) + return globalRand.Int63n(n) } // Intn returns, as an int, a non-negative pseudo-random number in [0,n) // from the default Source. func (r *DefaultRand) Intn(n int) int { - return rand.Intn(n) + return globalRand.Intn(n) } // Float64 returns, as a float64, a pseudo-random number in [0.0,1.0) // from the default Source. func (r *DefaultRand) Float64() float64 { - return rand.Float64() + return globalRand.Float64() } diff --git a/utils/timer.go b/utils/timer.go index 6cd1dfac..69654b64 100644 --- a/utils/timer.go +++ b/utils/timer.go @@ -9,14 +9,9 @@ package utils import ( - "math/rand" "time" ) -func init() { - rand.Seed(time.Now().UnixNano()) -} - // Timer is the same as time.Timer except that it has jitters. // A Timer must be created with NewTimer. type Timer struct { @@ -27,7 +22,7 @@ type Timer struct { // NewTimer creates a new Timer that will send the current time on its channel. func NewTimer(d, jitter time.Duration) *Timer { - t := time.NewTimer(d - time.Duration(rand.Int63n(int64(jitter)))) + t := time.NewTimer(d - time.Duration(globalRand.Int63n(int64(jitter)))) jitteredTimer := Timer{ t: t, @@ -46,5 +41,5 @@ func (j *Timer) C() <-chan time.Time { // Reset resets the timer. // Reset should be invoked only on stopped or expired timers with drained channels. func (j *Timer) Reset() { - j.t.Reset(j.d - time.Duration(rand.Int63n(int64(j.jitter)))) + j.t.Reset(j.d - time.Duration(globalRand.Int63n(int64(j.jitter)))) }