go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/data/rand/mathrand/mathrand.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package mathrand implements a mockable interface for math/rand.Rand. 16 // 17 // It is controllable through context.Context. You should use this instead of 18 // math/rand directly, to allow you to make deterministic tests. 19 package mathrand 20 21 import ( 22 "context" 23 "math/rand" 24 "sync" 25 ) 26 27 var key = "holds a rand.Rand for mathrand" 28 29 var ( 30 // globalOnce performs one-time global random variable initialization. 31 globalOnce sync.Once 32 33 // globalRandBase is the gloal *rand.Rand instance. It MUST NOT BE USED 34 // without holding globalRand's lock. 35 // 36 // globalRandBase must not be accessed directly; instead, it must be obtained 37 // through getGlobalRand to ensure that it is initialized. 38 globalRandBase *rand.Rand 39 40 // globalRand is the locking Rand implementation that wraps globalRandBase. 41 // 42 // globalRand must not be accessed directly; instead, it must be obtained 43 // through getGlobalRand to ensure that it is initialized. 44 globalRand *Locking 45 ) 46 47 // getGlobalRand returns globalRand and its Locking wrapper. This must be used 48 // instead of direct variable access in order to ensure that everything is 49 // initialized. 50 // 51 // We use a Once to perform this initialization so that we can enable 52 // applications to set the seed via rand.Seed if they wish. 53 func getGlobalRand() (*Locking, *rand.Rand) { 54 globalOnce.Do(func() { 55 globalRandBase = newRand() 56 globalRand = &Locking{R: wrapped{globalRandBase}} 57 }) 58 return globalRand, globalRandBase 59 } 60 61 func newRand() *rand.Rand { return rand.New(rand.NewSource(rand.Int63())) } 62 63 // getRand returns the Rand installed in c, or nil if no Rand is installed. 64 func getRand(ctx context.Context) Rand { 65 if r, ok := ctx.Value(&key).(Rand); ok { 66 return r 67 } 68 return nil 69 } 70 71 // Get gets a Rand from the Context. The resulting Rand is safe for concurrent 72 // use. 73 // 74 // If one hasn't been set, this will return a global Rand object backed by a 75 // shared rand.Rand singleton. Just like in "math/rand", rand.Seed can be called 76 // prior to using Get to set the seed used by this singleton. 77 func Get(ctx context.Context) Rand { 78 if r := getRand(ctx); r != nil { 79 return r 80 } 81 82 // Use the global instance. 83 gr, _ := getGlobalRand() 84 return gr 85 } 86 87 // Set sets the current *"math/rand".Rand object in the context. 88 // 89 // Useful for testing with a quick mock. The supplied *rand.Rand will be wrapped 90 // in a *Locking if necessary such that when it is returned from Get, it is 91 // safe for concurrent use. 92 func Set(ctx context.Context, mr *rand.Rand) context.Context { 93 var r Rand 94 if mr != nil { 95 r = wrapRand(mr) 96 } 97 return SetRand(ctx, r) 98 } 99 100 // SetRand sets the current Rand object in the context. 101 // 102 // Useful for testing with a quick mock. The supplied Rand will be wrapped 103 // in a *Locking if necessary such that when it is returned from Get, it is 104 // safe for concurrent use. 105 func SetRand(ctx context.Context, r Rand) context.Context { 106 if r != nil { 107 r = wrapLocking(r) 108 } 109 return context.WithValue(ctx, &key, r) 110 } 111 112 //////////////////////////////////////////////////////////////////////////////// 113 // Top-level convenience functions mirroring math/rand package API. 114 // 115 // This makes mathrand API more similar to math/rand API. 116 117 // Int63 returns a non-negative pseudo-random 63-bit integer as an int64 118 // from the source in the context or the shared global source. 119 func Int63(ctx context.Context) int64 { return Get(ctx).Int63() } 120 121 // Uint32 returns a pseudo-random 32-bit value as a uint32 from the source in 122 // the context or the shared global source. 123 func Uint32(ctx context.Context) uint32 { return Get(ctx).Uint32() } 124 125 // Int31 returns a non-negative pseudo-random 31-bit integer as an int32 from 126 // the source in the context or the shared global source. 127 func Int31(ctx context.Context) int32 { return Get(ctx).Int31() } 128 129 // Int returns a non-negative pseudo-random int from the source in the context 130 // or the shared global source. 131 func Int(ctx context.Context) int { return Get(ctx).Int() } 132 133 // Int63n returns, as an int64, a non-negative pseudo-random number in [0,n) 134 // from the source in the context or the shared global source. 135 // 136 // It panics if n <= 0. 137 func Int63n(ctx context.Context, n int64) int64 { return Get(ctx).Int63n(n) } 138 139 // Int31n returns, as an int32, a non-negative pseudo-random number in [0,n) 140 // from the source in the context or the shared global source. 141 // 142 // It panics if n <= 0. 143 func Int31n(ctx context.Context, n int32) int32 { return Get(ctx).Int31n(n) } 144 145 // Intn returns, as an int, a non-negative pseudo-random number in [0,n) from 146 // the source in the context or the shared global source. 147 // 148 // It panics if n <= 0. 149 func Intn(ctx context.Context, n int) int { return Get(ctx).Intn(n) } 150 151 // Float64 returns, as a float64, a pseudo-random number in [0.0,1.0) from 152 // the source in the context or the shared global source. 153 func Float64(ctx context.Context) float64 { return Get(ctx).Float64() } 154 155 // Float32 returns, as a float32, a pseudo-random number in [0.0,1.0) from 156 // the source in the context or the shared global source. 157 func Float32(ctx context.Context) float32 { return Get(ctx).Float32() } 158 159 // Perm returns, as a slice of n ints, a pseudo-random permutation of the 160 // integers [0,n) from the source in the context or the shared global source. 161 func Perm(ctx context.Context, n int) []int { return Get(ctx).Perm(n) } 162 163 // Read generates len(p) random bytes from the source in the context or 164 // the shared global source and writes them into p. It always returns len(p) 165 // and a nil error. 166 func Read(ctx context.Context, p []byte) (n int, err error) { return Get(ctx).Read(p) } 167 168 // NormFloat64 returns a normally distributed float64 in the range 169 // [-math.MaxFloat64, +math.MaxFloat64] with standard normal distribution 170 // (mean = 0, stddev = 1) from the source in the context or the shared global 171 // source. 172 // 173 // To produce a different normal distribution, callers can adjust the output 174 // using: 175 // 176 // sample = NormFloat64(ctx) * desiredStdDev + desiredMean 177 func NormFloat64(ctx context.Context) float64 { return Get(ctx).NormFloat64() } 178 179 // ExpFloat64 returns an exponentially distributed float64 in the range 180 // (0, +math.MaxFloat64] with an exponential distribution whose rate parameter 181 // (lambda) is 1 and whose mean is 1/lambda (1) from the source in the context 182 // or the shared global source. 183 // 184 // To produce a distribution with a different rate parameter, callers can adjust 185 // the output using: 186 // 187 // sample = ExpFloat64(ctx) / desiredRateParameter 188 func ExpFloat64(ctx context.Context) float64 { return Get(ctx).ExpFloat64() } 189 190 // WithGoRand invokes the supplied "fn" while holding an exclusive lock 191 // for it. This can be used by callers to pull and use a *rand.Rand instance 192 // out of the Context safely. 193 // 194 // The callback's r must not be retained or used outside of the scope of the 195 // callback. 196 func WithGoRand(ctx context.Context, fn func(r *rand.Rand) error) error { 197 if r := getRand(ctx); r != nil { 198 return r.WithGoRand(fn) 199 } 200 201 // Return our globalRandBase. We MUST hold globalRand's lock in order for this 202 // to be safe. 203 l, base := getGlobalRand() 204 l.Lock() 205 defer l.Unlock() 206 return fn(base) 207 }