Skip to content

Commit 2028077

Browse files
committed
runtime: randomize scheduling in -race mode
Basic randomization of goroutine scheduling for -race mode. It is probably possible to do much better (there's a paper linked in the issue that I haven't read, for example), but this suffices to introduce at least some unpredictability into the scheduling order. The goal here is to have _something_ for Go 1.5, so that we don't start hitting more of these scheduling order-dependent bugs if we change the scheduler order again in Go 1.6. For #11372. Change-Id: Idf1154123fbd5b7a1ee4d339e93f97635cc2bacb Reviewed-on: https://go-review.googlesource.com/11795 Reviewed-by: Austin Clements <[email protected]>
1 parent 0409158 commit 2028077

File tree

2 files changed

+70
-0
lines changed

2 files changed

+70
-0
lines changed

src/runtime/proc1.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3357,12 +3357,27 @@ func runqempty(_p_ *p) bool {
33573357
return _p_.runqhead == _p_.runqtail && _p_.runnext == 0
33583358
}
33593359

3360+
// To shake out latent assumptions about scheduling order,
3361+
// we introduce some randomness into scheduling decisions
3362+
// when running with the race detector.
3363+
// The need for this was made obvious by changing the
3364+
// (deterministic) scheduling order in Go 1.5 and breaking
3365+
// many poorly-written tests.
3366+
// With the randomness here, as long as the tests pass
3367+
// consistently with -race, they shouldn't have latent scheduling
3368+
// assumptions.
3369+
const randomizeScheduler = raceenabled
3370+
33603371
// runqput tries to put g on the local runnable queue.
33613372
// If next if false, runqput adds g to the tail of the runnable queue.
33623373
// If next is true, runqput puts g in the _p_.runnext slot.
33633374
// If the run queue is full, runnext puts g on the global queue.
33643375
// Executed only by the owner P.
33653376
func runqput(_p_ *p, gp *g, next bool) {
3377+
if randomizeScheduler && next && fastrand1()%2 == 0 {
3378+
next = false
3379+
}
3380+
33663381
if next {
33673382
retryNext:
33683383
oldnext := _p_.runnext
@@ -3410,6 +3425,13 @@ func runqputslow(_p_ *p, gp *g, h, t uint32) bool {
34103425
}
34113426
batch[n] = gp
34123427

3428+
if randomizeScheduler {
3429+
for i := uint32(1); i <= n; i++ {
3430+
j := fastrand1() % (i + 1)
3431+
batch[i], batch[j] = batch[j], batch[i]
3432+
}
3433+
}
3434+
34133435
// Link the goroutines.
34143436
for i := uint32(0); i < n; i++ {
34153437
batch[i].schedlink.set(batch[i+1])

src/runtime/race/sched_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2015 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+
// +build race
6+
7+
package race_test
8+
9+
import (
10+
"bytes"
11+
"fmt"
12+
"reflect"
13+
"runtime"
14+
"testing"
15+
)
16+
17+
func TestRandomScheduling(t *testing.T) {
18+
// Scheduler is most consistent with GOMAXPROCS=1.
19+
// Use that to make the test most likely to fail.
20+
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
21+
const N = 10
22+
out := make([][]int, N)
23+
for i := 0; i < N; i++ {
24+
c := make(chan int, N)
25+
for j := 0; j < N; j++ {
26+
go func(j int) {
27+
c <- j
28+
}(j)
29+
}
30+
row := make([]int, N)
31+
for j := 0; j < N; j++ {
32+
row[j] = <-c
33+
}
34+
out[i] = row
35+
}
36+
37+
for i := 0; i < N; i++ {
38+
if !reflect.DeepEqual(out[0], out[i]) {
39+
return // found a different order
40+
}
41+
}
42+
43+
var buf bytes.Buffer
44+
for i := 0; i < N; i++ {
45+
fmt.Fprintf(&buf, "%v\n", out[i])
46+
}
47+
t.Fatalf("consistent goroutine execution order:\n%v", buf.String())
48+
}

0 commit comments

Comments
 (0)