Skip to content

Commit d93cb46

Browse files
committed
[release-branch.go1.9] runtime: use simple, more robust fastrandn
CL 36932 (speed up fastrandn) made it faster but introduced bad interference with some properties of fastrand itself, making fastrandn not very random in certain ways. In particular, certain selects are demonstrably unfair. For Go 1.10 the new faster fastrandn has induced a new fastrand, which in turn has caused other follow-on bugs that are still being discovered and fixed. For Go 1.9.2, just go back to the barely slower % implementation that we used in Go 1.8 and earlier. This should restore fairness in select and any other problems caused by the clever fastrandn. The test in this CL is copied from CL 62530. Fixes #22253. Change-Id: Ibcf948a7bce981452e05c90dbdac122043f6f813 Reviewed-on: https://go-review.googlesource.com/70991 Run-TryBot: Russ Cox <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent 78952c0 commit d93cb46

File tree

2 files changed

+62
-3
lines changed

2 files changed

+62
-3
lines changed

src/runtime/chan_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package runtime_test
66

77
import (
8+
"math"
89
"runtime"
910
"sync"
1011
"sync/atomic"
@@ -430,6 +431,62 @@ func TestSelectStress(t *testing.T) {
430431
wg.Wait()
431432
}
432433

434+
func TestSelectFairness(t *testing.T) {
435+
const trials = 10000
436+
c1 := make(chan byte, trials+1)
437+
c2 := make(chan byte, trials+1)
438+
for i := 0; i < trials+1; i++ {
439+
c1 <- 1
440+
c2 <- 2
441+
}
442+
c3 := make(chan byte)
443+
c4 := make(chan byte)
444+
out := make(chan byte)
445+
done := make(chan byte)
446+
var wg sync.WaitGroup
447+
wg.Add(1)
448+
go func() {
449+
defer wg.Done()
450+
for {
451+
var b byte
452+
select {
453+
case b = <-c3:
454+
case b = <-c4:
455+
case b = <-c1:
456+
case b = <-c2:
457+
}
458+
select {
459+
case out <- b:
460+
case <-done:
461+
return
462+
}
463+
}
464+
}()
465+
cnt1, cnt2 := 0, 0
466+
for i := 0; i < trials; i++ {
467+
switch b := <-out; b {
468+
case 1:
469+
cnt1++
470+
case 2:
471+
cnt2++
472+
default:
473+
t.Fatalf("unexpected value %d on channel", b)
474+
}
475+
}
476+
// If the select in the goroutine is fair,
477+
// cnt1 and cnt2 should be about the same value.
478+
// With 10,000 trials, the expected margin of error at
479+
// a confidence level of five nines is 4.4172 / (2 * Sqrt(10000)).
480+
r := float64(cnt1) / trials
481+
e := math.Abs(r - 0.5)
482+
t.Log(cnt1, cnt2, r, e)
483+
if e > 4.4172/(2*math.Sqrt(trials)) {
484+
t.Errorf("unfair select: in %d trials, results were %d, %d", trials, cnt1, cnt2)
485+
}
486+
close(done)
487+
wg.Wait()
488+
}
489+
433490
func TestChanSendInterface(t *testing.T) {
434491
type mt struct{}
435492
m := &mt{}

src/runtime/stubs.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,11 @@ func fastrand() uint32 {
105105

106106
//go:nosplit
107107
func fastrandn(n uint32) uint32 {
108-
// This is similar to fastrand() % n, but faster.
109-
// See http://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
110-
return uint32(uint64(fastrand()) * uint64(n) >> 32)
108+
// Don't be clever.
109+
// fastrand is not good enough for cleverness.
110+
// Just use mod.
111+
// See golang.org/issue/21806.
112+
return fastrand() % n
111113
}
112114

113115
//go:linkname sync_fastrand sync.fastrand

0 commit comments

Comments
 (0)