mirror of
https://github.com/golang/go
synced 2024-11-23 07:20:06 -07:00
math/rand: auto-seed global source
Implement proposal #54880, to automatically seed the global source. The justification for this not being a breaking change is that any use of the global source in a package's init function or exported API clearly must be valid - that is, if a package changes how much randomness it consumes at init time or in an exported API, that clearly isn't the kind of breaking change that requires issuing a v2 of that package. That kind of per-package change in the position of the global source is indistinguishable from seeding the global source differently. So if the per-package change is valid, so is auto-seeding. And then, of course, auto-seeding means that packages will be far less likely to depend on the specific results of the global source and therefore not break when those kinds of per-package changes happen in the future. Seed(1) can be called in programs that need the old sequence from the global source and want to restore the old behavior. Of course, those programs will still be broken by the per-package changes just described, and it would be better for them to allocate local sources rather than continue to use the global one. Fixes #54880. Change-Id: Ib9dc3307b97f7a45587a9cc50d81f919d3edc7ae Reviewed-on: https://go-review.googlesource.com/c/go/+/443058 Reviewed-by: Austin Clements <austin@google.com> Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Auto-Submit: Russ Cox <rsc@golang.org>
This commit is contained in:
parent
4c9006e45f
commit
90a67d052e
40
src/math/rand/auto_test.go
Normal file
40
src/math/rand/auto_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2022 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 (
|
||||
. "math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// This test is first, in its own file with an alphabetically early name,
|
||||
// to try to make sure that it runs early. It has the best chance of
|
||||
// detecting deterministic seeding if it's the first test that runs.
|
||||
|
||||
func TestAuto(t *testing.T) {
|
||||
// Pull out 10 int64s from the global source
|
||||
// and then check that they don't appear in that
|
||||
// order in the deterministic Seed(1) result.
|
||||
var out []int64
|
||||
for i := 0; i < 10; i++ {
|
||||
out = append(out, Int63())
|
||||
}
|
||||
|
||||
// Look for out in Seed(1)'s output.
|
||||
// Strictly speaking, we should look for them in order,
|
||||
// but this is good enough and not significantly more
|
||||
// likely to have a false positive.
|
||||
Seed(1)
|
||||
found := 0
|
||||
for i := 0; i < 1000; i++ {
|
||||
x := Int63()
|
||||
if x == out[found] {
|
||||
found++
|
||||
if found == len(out) {
|
||||
t.Fatalf("found unseeded output in Seed(1) output")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,22 +5,28 @@
|
||||
// Package rand implements pseudo-random number generators unsuitable for
|
||||
// security-sensitive work.
|
||||
//
|
||||
// Random numbers are generated by a Source. Top-level functions, such as
|
||||
// Float64 and Int, use a default shared Source that produces a deterministic
|
||||
// sequence of values each time a program is run. Use the Seed function to
|
||||
// initialize the default Source if different behavior is required for each run.
|
||||
// The default Source is safe for concurrent use by multiple goroutines, but
|
||||
// Sources created by NewSource are not.
|
||||
// Random numbers are generated by a [Source], usually wrapped in a [Rand].
|
||||
// Both types should be used by a single goroutine at a time: sharing among
|
||||
// multiple goroutines requires some kind of synchronization.
|
||||
//
|
||||
// Top-level functions, such as [Float64] and [Int],
|
||||
// are safe for concurrent use by multiple goroutines.
|
||||
//
|
||||
// This package's outputs might be easily predictable regardless of how it's
|
||||
// seeded. For random numbers suitable for security-sensitive work, see the
|
||||
// crypto/rand package.
|
||||
package rand
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"internal/godebug"
|
||||
"sync"
|
||||
_ "unsafe" // for go:linkname
|
||||
)
|
||||
|
||||
// A Source represents a source of uniformly-distributed
|
||||
// pseudo-random int64 values in the range [0, 1<<63).
|
||||
//
|
||||
// A Source is not safe for concurrent use by multiple goroutines.
|
||||
type Source interface {
|
||||
Int63() int64
|
||||
Seed(seed int64)
|
||||
@ -298,10 +304,23 @@ func read(p []byte, src Source, readVal *int64, readPos *int8) (n int, err error
|
||||
var globalRand = New(new(lockedSource))
|
||||
|
||||
// Seed uses the provided seed value to initialize the default Source to a
|
||||
// deterministic state. If Seed is not called, the generator behaves as
|
||||
// if seeded by Seed(1). Seed values that have the same remainder when
|
||||
// deterministic state. Seed values that have the same remainder when
|
||||
// divided by 2³¹-1 generate the same pseudo-random sequence.
|
||||
// Seed, unlike the Rand.Seed method, is safe for concurrent use.
|
||||
//
|
||||
// If Seed is not called, the generator is seeded randomly at program startup.
|
||||
//
|
||||
// Prior to Go 1.20, the generator was seeded like Seed(1) at program startup.
|
||||
// To force the old behavior, call Seed(1) at program startup.
|
||||
// Alternately, set GODEBUG=randautoseed=0 in the environment
|
||||
// before making any calls to functions in this package.
|
||||
//
|
||||
// Note: Programs that call Seed and then expect a specific sequence
|
||||
// of results from the global random source (using functions such as Int)
|
||||
// can be broken when a dependency changes how much it consumes
|
||||
// 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) }
|
||||
|
||||
// Int63 returns a non-negative pseudo-random 63-bit integer as an int64
|
||||
@ -384,11 +403,20 @@ type lockedSource struct {
|
||||
s *rngSource // nil if not yet allocated
|
||||
}
|
||||
|
||||
//go:linkname fastrand64
|
||||
func fastrand64() uint64
|
||||
|
||||
// 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 {
|
||||
r.s = newSource(1)
|
||||
var seed int64
|
||||
if godebug.Get("randautoseed") == "0" {
|
||||
seed = 1
|
||||
} else {
|
||||
seed = int64(fastrand64())
|
||||
}
|
||||
r.s = newSource(seed)
|
||||
}
|
||||
return r.s
|
||||
}
|
||||
|
@ -196,6 +196,9 @@ func fastrandu() uint {
|
||||
return uint(fastrand64())
|
||||
}
|
||||
|
||||
//go:linkname rand_fastrand64 math/rand.fastrand64
|
||||
func rand_fastrand64() uint64 { return fastrand64() }
|
||||
|
||||
//go:linkname sync_fastrandn sync.fastrandn
|
||||
func sync_fastrandn(n uint32) uint32 { return fastrandn(n) }
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user