1
0
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:
Russ Cox 2022-10-04 12:20:18 -04:00 committed by Gopher Robot
parent 4c9006e45f
commit 90a67d052e
3 changed files with 81 additions and 10 deletions

View 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")
}
}
}
}

View File

@ -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
}

View File

@ -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) }