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  }