--- /dev/null
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package rand_test
+
+import (
+ "fmt"
+ "internal/race"
+ "internal/testenv"
+ . "math/rand"
+ "os"
+ "runtime"
+ "strconv"
+ "sync"
+ "testing"
+)
+
+// Test that racy access to the default functions behaves reasonably.
+func TestDefaultRace(t *testing.T) {
+ // Skip the test in short mode, but even in short mode run
+ // the test if we are using the race detector, because part
+ // of this is to see whether the race detector reports any problems.
+ if testing.Short() && !race.Enabled {
+ t.Skip("skipping starting another executable in short mode")
+ }
+
+ const env = "GO_RAND_TEST_HELPER_CODE"
+ if v := os.Getenv(env); v != "" {
+ doDefaultTest(t, v)
+ return
+ }
+
+ t.Parallel()
+
+ for i := 0; i < 6; i++ {
+ i := i
+ t.Run(strconv.Itoa(i), func(t *testing.T) {
+ t.Parallel()
+ exe, err := os.Executable()
+ if err != nil {
+ exe = os.Args[0]
+ }
+ cmd := testenv.Command(t, exe, "-test.run=TestDefaultRace")
+ cmd = testenv.CleanCmdEnv(cmd)
+ cmd.Env = append(cmd.Env, fmt.Sprintf("GO_RAND_TEST_HELPER_CODE=%d", i/2))
+ if i%2 != 0 {
+ cmd.Env = append(cmd.Env, "GODEBUG=randautoseed=0")
+ }
+ out, err := cmd.CombinedOutput()
+ if len(out) > 0 {
+ t.Logf("%s", out)
+ }
+ if err != nil {
+ t.Error(err)
+ }
+ })
+ }
+}
+
+// doDefaultTest should be run before there have been any calls to the
+// top-level math/rand functions. Make sure that we can make concurrent
+// calls to top-level functions and to Seed without any duplicate values.
+// This will also give the race detector a change to report any problems.
+func doDefaultTest(t *testing.T, v string) {
+ code, err := strconv.Atoi(v)
+ if err != nil {
+ t.Fatalf("internal error: unrecognized code %q", v)
+ }
+
+ goroutines := runtime.GOMAXPROCS(0)
+ if goroutines < 4 {
+ goroutines = 4
+ }
+
+ ch := make(chan uint64, goroutines*3)
+ var wg sync.WaitGroup
+
+ // The various tests below should not cause race detector reports
+ // and should not produce duplicate results.
+ //
+ // Note: these tests can theoretically fail when using fastrand64
+ // in that it is possible to coincidentally get the same random
+ // number twice. That could happen something like 1 / 2**64 times,
+ // which is rare enough that it may never happen. We don't worry
+ // about that case.
+
+ switch code {
+ case 0:
+ // Call Seed and Uint64 concurrently.
+ wg.Add(goroutines)
+ for i := 0; i < goroutines; i++ {
+ go func(s int64) {
+ defer wg.Done()
+ Seed(s)
+ }(int64(i) + 100)
+ }
+ wg.Add(goroutines)
+ for i := 0; i < goroutines; i++ {
+ go func() {
+ defer wg.Done()
+ ch <- Uint64()
+ }()
+ }
+ case 1:
+ // Call Uint64 concurrently with no Seed.
+ wg.Add(goroutines)
+ for i := 0; i < goroutines; i++ {
+ go func() {
+ defer wg.Done()
+ ch <- Uint64()
+ }()
+ }
+ case 2:
+ // Start with Uint64 to pick the fast source, then call
+ // Seed and Uint64 concurrently.
+ ch <- Uint64()
+ wg.Add(goroutines)
+ for i := 0; i < goroutines; i++ {
+ go func(s int64) {
+ defer wg.Done()
+ Seed(s)
+ }(int64(i) + 100)
+ }
+ wg.Add(goroutines)
+ for i := 0; i < goroutines; i++ {
+ go func() {
+ defer wg.Done()
+ ch <- Uint64()
+ }()
+ }
+ default:
+ t.Fatalf("internal error: unrecognized code %d", code)
+ }
+
+ go func() {
+ wg.Wait()
+ close(ch)
+ }()
+
+ m := make(map[uint64]bool)
+ for i := range ch {
+ if m[i] {
+ t.Errorf("saw %d twice", i)
+ }
+ m[i] = true
+ }
+}
import (
"internal/godebug"
"sync"
+ "sync/atomic"
_ "unsafe" // for go:linkname
)
// always returns len(p) and a nil error.
// Read should not be called concurrently with any other Rand method.
func (r *Rand) Read(p []byte) (n int, err error) {
- if lk, ok := r.src.(*lockedSource); ok {
- return lk.read(p, &r.readVal, &r.readPos)
+ switch src := r.src.(type) {
+ case *lockedSource:
+ return src.read(p, &r.readVal, &r.readPos)
+ case *fastSource:
+ return src.read(p, &r.readVal, &r.readPos)
}
return read(p, r.src, &r.readVal, &r.readPos)
}
* Top-level convenience functions
*/
-var globalRand = New(new(lockedSource))
+// globalRandGenerator is the source of random numbers for the top-level
+// convenience functions. When possible it uses the runtime fastrand64
+// function to avoid locking. This is not possible if the user called Seed,
+// either explicitly or implicitly via GODEBUG=randautoseed=0.
+var globalRandGenerator atomic.Pointer[Rand]
+
+var randautoseed = godebug.New("randautoseed")
+
+// globalRand returns the generator to use for the top-level convenience
+// functions.
+func globalRand() *Rand {
+ if r := globalRandGenerator.Load(); r != nil {
+ return r
+ }
+
+ // This is the first call. Initialize based on GODEBUG.
+ var r *Rand
+ if randautoseed.Value() == "0" {
+ randautoseed.IncNonDefault()
+ r = New(new(lockedSource))
+ r.Seed(1)
+ } else {
+ r = &Rand{
+ src: &fastSource{},
+ s64: &fastSource{},
+ }
+ }
+
+ if !globalRandGenerator.CompareAndSwap(nil, r) {
+ // Two different goroutines called some top-level
+ // function at the same time. While the results in
+ // that case are unpredictable, if we just use r here,
+ // and we are using a seed, we will most likely return
+ // the same value for both calls. That doesn't seem ideal.
+ // Just use the first one to get in.
+ return globalRandGenerator.Load()
+ }
+
+ return r
+}
+
+//go:linkname fastrand64
+func fastrand64() uint64
+
+// fastSource is an implementation of Source64 that uses the runtime
+// fastrand functions.
+type fastSource struct {
+ // The mutex is used to avoid race conditions in Read.
+ mu sync.Mutex
+}
+
+func (*fastSource) Int63() int64 {
+ return int64(fastrand64() & rngMask)
+}
+
+func (*fastSource) Seed(int64) {
+ panic("internal error: call to fastSource.Seed")
+}
+
+func (*fastSource) Uint64() uint64 {
+ return fastrand64()
+}
+
+func (fs *fastSource) read(p []byte, readVal *int64, readPos *int8) (n int, err error) {
+ fs.mu.Lock()
+ n, err = read(p, fs, readVal, readPos)
+ fs.mu.Unlock()
+ return
+}
// Seed uses the provided seed value to initialize the default Source to a
// deterministic state. Seed values that have the same remainder when
// from the global random source. To avoid such breakages, programs
// that need a specific result sequence should use NewRand(NewSource(seed))
// to obtain a random generator that other packages cannot access.
-func Seed(seed int64) { globalRand.Seed(seed) }
+func Seed(seed int64) {
+ orig := globalRandGenerator.Load()
+
+ // If we are already using a lockedSource, we can just re-seed it.
+ if orig != nil {
+ if _, ok := orig.src.(*lockedSource); ok {
+ orig.Seed(seed)
+ return
+ }
+ }
+
+ // Otherwise either
+ // 1) orig == nil, which is the normal case when Seed is the first
+ // top-level function to be called, or
+ // 2) orig is already a fastSource, in which case we need to change
+ // to a lockedSource.
+ // Either way we do the same thing.
+
+ r := New(new(lockedSource))
+ r.Seed(seed)
+
+ if !globalRandGenerator.CompareAndSwap(orig, r) {
+ // Something changed underfoot. Retry to be safe.
+ Seed(seed)
+ }
+}
// Int63 returns a non-negative pseudo-random 63-bit integer as an int64
// from the default Source.
-func Int63() int64 { return globalRand.Int63() }
+func Int63() int64 { return globalRand().Int63() }
// Uint32 returns a pseudo-random 32-bit value as a uint32
// from the default Source.
-func Uint32() uint32 { return globalRand.Uint32() }
+func Uint32() uint32 { return globalRand().Uint32() }
// Uint64 returns a pseudo-random 64-bit value as a uint64
// from the default Source.
-func Uint64() uint64 { return globalRand.Uint64() }
+func Uint64() uint64 { return globalRand().Uint64() }
// Int31 returns a non-negative pseudo-random 31-bit integer as an int32
// from the default Source.
-func Int31() int32 { return globalRand.Int31() }
+func Int31() int32 { return globalRand().Int31() }
// Int returns a non-negative pseudo-random int from the default Source.
-func Int() int { return globalRand.Int() }
+func Int() int { return globalRand().Int() }
// Int63n returns, as an int64, a non-negative pseudo-random number in the half-open interval [0,n)
// from the default Source.
// It panics if n <= 0.
-func Int63n(n int64) int64 { return globalRand.Int63n(n) }
+func Int63n(n int64) int64 { return globalRand().Int63n(n) }
// Int31n returns, as an int32, a non-negative pseudo-random number in the half-open interval [0,n)
// from the default Source.
// It panics if n <= 0.
-func Int31n(n int32) int32 { return globalRand.Int31n(n) }
+func Int31n(n int32) int32 { return globalRand().Int31n(n) }
// Intn returns, as an int, a non-negative pseudo-random number in the half-open interval [0,n)
// from the default Source.
// It panics if n <= 0.
-func Intn(n int) int { return globalRand.Intn(n) }
+func Intn(n int) int { return globalRand().Intn(n) }
// Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0)
// from the default Source.
-func Float64() float64 { return globalRand.Float64() }
+func Float64() float64 { return globalRand().Float64() }
// Float32 returns, as a float32, a pseudo-random number in the half-open interval [0.0,1.0)
// from the default Source.
-func Float32() float32 { return globalRand.Float32() }
+func Float32() float32 { return globalRand().Float32() }
// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers
// in the half-open interval [0,n) from the default Source.
-func Perm(n int) []int { return globalRand.Perm(n) }
+func Perm(n int) []int { return globalRand().Perm(n) }
// Shuffle pseudo-randomizes the order of elements using the default Source.
// n is the number of elements. Shuffle panics if n < 0.
// swap swaps the elements with indexes i and j.
-func Shuffle(n int, swap func(i, j int)) { globalRand.Shuffle(n, swap) }
+func Shuffle(n int, swap func(i, j int)) { globalRand().Shuffle(n, swap) }
// Read generates len(p) random bytes from the default Source and
// writes them into p. It always returns len(p) and a nil error.
// Read, unlike the Rand.Read method, is safe for concurrent use.
//
// Deprecated: For almost all use cases, crypto/rand.Read is more appropriate.
-func Read(p []byte) (n int, err error) { return globalRand.Read(p) }
+func Read(p []byte) (n int, err error) { return globalRand().Read(p) }
// NormFloat64 returns a normally distributed float64 in the range
// [-math.MaxFloat64, +math.MaxFloat64] with
// adjust the output using:
//
// sample = NormFloat64() * desiredStdDev + desiredMean
-func NormFloat64() float64 { return globalRand.NormFloat64() }
+func NormFloat64() float64 { return globalRand().NormFloat64() }
// ExpFloat64 returns an exponentially distributed float64 in the range
// (0, +math.MaxFloat64] with an exponential distribution whose rate parameter
// callers can adjust the output using:
//
// sample = ExpFloat64() / desiredRateParameter
-func ExpFloat64() float64 { return globalRand.ExpFloat64() }
+func ExpFloat64() float64 { return globalRand().ExpFloat64() }
type lockedSource struct {
lk sync.Mutex
- s *rngSource // nil if not yet allocated
-}
-
-//go:linkname fastrand64
-func fastrand64() uint64
-
-var randautoseed = godebug.New("randautoseed")
-
-// source returns r.s, allocating and seeding it if needed.
-// The caller must have locked r.
-func (r *lockedSource) source() *rngSource {
- if r.s == nil {
- var seed int64
- if randautoseed.Value() == "0" {
- randautoseed.IncNonDefault()
- seed = 1
- } else {
- seed = int64(fastrand64())
- }
- r.s = newSource(seed)
- }
- return r.s
+ s *rngSource
}
func (r *lockedSource) Int63() (n int64) {
r.lk.Lock()
- n = r.source().Int63()
+ n = r.s.Int63()
r.lk.Unlock()
return
}
func (r *lockedSource) Uint64() (n uint64) {
r.lk.Lock()
- n = r.source().Uint64()
+ n = r.s.Uint64()
r.lk.Unlock()
return
}
// 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.source(), readVal, readPos)
+ n, err = read(p, r.s, readVal, readPos)
r.lk.Unlock()
return
}