From df01ccffcb75501687044e60d2184a4307a206d6 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Mon, 29 Jul 2019 23:24:04 -0400 Subject: [PATCH] mrand: implement NewSyncRand and use that for DefaultRand, to make it thread-safe --- mrand/lockedSource.go | 67 +++++++++++++++++++++++++++++++++++++++++++ mrand/mrand.go | 12 +++++++- 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 mrand/lockedSource.go diff --git a/mrand/lockedSource.go b/mrand/lockedSource.go new file mode 100644 index 0000000..019618c --- /dev/null +++ b/mrand/lockedSource.go @@ -0,0 +1,67 @@ +package mrand + +import ( + "math/rand" + "sync" +) + +// Everything in this file is taken from the math/rand package, which really +// ought to expose lockedSource publicly. + +func read(p []byte, int63 func() int64, readVal *int64, readPos *int8) (n int, err error) { + pos := *readPos + val := *readVal + for n = 0; n < len(p); n++ { + if pos == 0 { + val = int63() + pos = 7 + } + p[n] = byte(val) + val >>= 8 + pos-- + } + *readPos = pos + *readVal = val + return +} + +type lockedSource struct { + lk sync.Mutex + src rand.Source64 +} + +func (r *lockedSource) Int63() (n int64) { + r.lk.Lock() + n = r.src.Int63() + r.lk.Unlock() + return +} + +func (r *lockedSource) Uint64() (n uint64) { + r.lk.Lock() + n = r.src.Uint64() + r.lk.Unlock() + return +} + +func (r *lockedSource) Seed(seed int64) { + r.lk.Lock() + r.src.Seed(seed) + r.lk.Unlock() +} + +// seedPos implements Seed for a lockedSource without a race condition. +func (r *lockedSource) seedPos(seed int64, readPos *int8) { + r.lk.Lock() + r.src.Seed(seed) + *readPos = 0 + r.lk.Unlock() +} + +// read implements Read for a lockedSource without a race condition. +func (r *lockedSource) read(p []byte, readVal *int64, readPos *int8) (n int, err error) { + r.lk.Lock() + n, err = read(p, r.src.Int63, readVal, readPos) + r.lk.Unlock() + return +} diff --git a/mrand/mrand.go b/mrand/mrand.go index 6ff5242..8df881e 100644 --- a/mrand/mrand.go +++ b/mrand/mrand.go @@ -14,6 +14,16 @@ type Rand struct { *rand.Rand } +// NewSyncRand initializes and returns a new Rand instance using the given +// Source. The returned Rand will be safe for concurrent use. +// +// This will panic if the given Source doesn't implement rand.Source64. +func NewSyncRand(src rand.Source) Rand { + return Rand{ + Rand: rand.New(&lockedSource{src: src.(rand.Source64)}), + } +} + // Bytes returns n random bytes. func (r Rand) Bytes(n int) []byte { b := make([]byte, n) @@ -67,7 +77,7 @@ func (r Rand) Element(slice interface{}, weight func(i int) uint64) interface{} // DefaultRand is an instance off Rand whose methods are directly exported by // this package for convenience. -var DefaultRand = Rand{Rand: rand.New(rand.NewSource(time.Now().UnixNano()))} +var DefaultRand = NewSyncRand(rand.NewSource(time.Now().UnixNano())) // Methods off DefaultRand exported to the top level of this package. var (