Skip to content

Commit 90a67d0

Browse files
rscgopherbot
authored andcommitted
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>
1 parent 4c9006e commit 90a67d0

File tree

3 files changed

+81
-10
lines changed

3 files changed

+81
-10
lines changed

src/math/rand/auto_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package rand_test
6+
7+
import (
8+
. "math/rand"
9+
"testing"
10+
)
11+
12+
// This test is first, in its own file with an alphabetically early name,
13+
// to try to make sure that it runs early. It has the best chance of
14+
// detecting deterministic seeding if it's the first test that runs.
15+
16+
func TestAuto(t *testing.T) {
17+
// Pull out 10 int64s from the global source
18+
// and then check that they don't appear in that
19+
// order in the deterministic Seed(1) result.
20+
var out []int64
21+
for i := 0; i < 10; i++ {
22+
out = append(out, Int63())
23+
}
24+
25+
// Look for out in Seed(1)'s output.
26+
// Strictly speaking, we should look for them in order,
27+
// but this is good enough and not significantly more
28+
// likely to have a false positive.
29+
Seed(1)
30+
found := 0
31+
for i := 0; i < 1000; i++ {
32+
x := Int63()
33+
if x == out[found] {
34+
found++
35+
if found == len(out) {
36+
t.Fatalf("found unseeded output in Seed(1) output")
37+
}
38+
}
39+
}
40+
}

src/math/rand/rand.go

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,28 @@
55
// Package rand implements pseudo-random number generators unsuitable for
66
// security-sensitive work.
77
//
8-
// Random numbers are generated by a Source. Top-level functions, such as
9-
// Float64 and Int, use a default shared Source that produces a deterministic
10-
// sequence of values each time a program is run. Use the Seed function to
11-
// initialize the default Source if different behavior is required for each run.
12-
// The default Source is safe for concurrent use by multiple goroutines, but
13-
// Sources created by NewSource are not.
8+
// Random numbers are generated by a [Source], usually wrapped in a [Rand].
9+
// Both types should be used by a single goroutine at a time: sharing among
10+
// multiple goroutines requires some kind of synchronization.
11+
//
12+
// Top-level functions, such as [Float64] and [Int],
13+
// are safe for concurrent use by multiple goroutines.
1414
//
1515
// This package's outputs might be easily predictable regardless of how it's
1616
// seeded. For random numbers suitable for security-sensitive work, see the
1717
// crypto/rand package.
1818
package rand
1919

20-
import "sync"
20+
import (
21+
"internal/godebug"
22+
"sync"
23+
_ "unsafe" // for go:linkname
24+
)
2125

2226
// A Source represents a source of uniformly-distributed
2327
// pseudo-random int64 values in the range [0, 1<<63).
28+
//
29+
// A Source is not safe for concurrent use by multiple goroutines.
2430
type Source interface {
2531
Int63() int64
2632
Seed(seed int64)
@@ -298,10 +304,23 @@ func read(p []byte, src Source, readVal *int64, readPos *int8) (n int, err error
298304
var globalRand = New(new(lockedSource))
299305

300306
// Seed uses the provided seed value to initialize the default Source to a
301-
// deterministic state. If Seed is not called, the generator behaves as
302-
// if seeded by Seed(1). Seed values that have the same remainder when
307+
// deterministic state. Seed values that have the same remainder when
303308
// divided by 2³¹-1 generate the same pseudo-random sequence.
304309
// Seed, unlike the Rand.Seed method, is safe for concurrent use.
310+
//
311+
// If Seed is not called, the generator is seeded randomly at program startup.
312+
//
313+
// Prior to Go 1.20, the generator was seeded like Seed(1) at program startup.
314+
// To force the old behavior, call Seed(1) at program startup.
315+
// Alternately, set GODEBUG=randautoseed=0 in the environment
316+
// before making any calls to functions in this package.
317+
//
318+
// Note: Programs that call Seed and then expect a specific sequence
319+
// of results from the global random source (using functions such as Int)
320+
// can be broken when a dependency changes how much it consumes
321+
// from the global random source. To avoid such breakages, programs
322+
// that need a specific result sequence should use NewRand(NewSource(seed))
323+
// to obtain a random generator that other packages cannot access.
305324
func Seed(seed int64) { globalRand.Seed(seed) }
306325

307326
// Int63 returns a non-negative pseudo-random 63-bit integer as an int64
@@ -384,11 +403,20 @@ type lockedSource struct {
384403
s *rngSource // nil if not yet allocated
385404
}
386405

406+
//go:linkname fastrand64
407+
func fastrand64() uint64
408+
387409
// source returns r.s, allocating and seeding it if needed.
388410
// The caller must have locked r.
389411
func (r *lockedSource) source() *rngSource {
390412
if r.s == nil {
391-
r.s = newSource(1)
413+
var seed int64
414+
if godebug.Get("randautoseed") == "0" {
415+
seed = 1
416+
} else {
417+
seed = int64(fastrand64())
418+
}
419+
r.s = newSource(seed)
392420
}
393421
return r.s
394422
}

src/runtime/stubs.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ func fastrandu() uint {
196196
return uint(fastrand64())
197197
}
198198

199+
//go:linkname rand_fastrand64 math/rand.fastrand64
200+
func rand_fastrand64() uint64 { return fastrand64() }
201+
199202
//go:linkname sync_fastrandn sync.fastrandn
200203
func sync_fastrandn(n uint32) uint32 { return fastrandn(n) }
201204

0 commit comments

Comments
 (0)