1
0
mirror of https://github.com/golang/go synced 2024-11-23 14:40:02 -07:00

context: fix a flaky timeout in TestLayersTimeout

In CL 223019, I reduced the short timeout in the testLayers helper to
be even shorter than it was. That exposed a racy (time-dependent)
select later in the function, which failed in one of the slower
builders (android-386-emu).

Also streamline the test to make it easier to test with a very high -count flag:
- Run tests that sleep for shortDuration in parallel to reduce latency.
- Use shorter durations in examples to reduce test running time.
- Avoid mutating global state (in package math/rand) in testLayers.

After this change (but not before it),
'go test -run=TestLayersTimeout -count=100000 context' passes on my workstation.

Fixes #38161

Change-Id: Iaf4abe7ac308b2100d8828267cda9f4f8ae4be82
Reviewed-on: https://go-review.googlesource.com/c/go/+/226457
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
Bryan C. Mills 2020-03-30 14:04:08 -04:00
parent 5db079d2e5
commit 5d2ddcd3f5
2 changed files with 22 additions and 11 deletions

View File

@ -27,6 +27,7 @@ type testingT interface {
Log(args ...interface{}) Log(args ...interface{})
Logf(format string, args ...interface{}) Logf(format string, args ...interface{})
Name() string Name() string
Parallel()
Skip(args ...interface{}) Skip(args ...interface{})
SkipNow() SkipNow()
Skipf(format string, args ...interface{}) Skipf(format string, args ...interface{})
@ -284,6 +285,8 @@ func testDeadline(c Context, name string, t testingT) {
} }
func XTestDeadline(t testingT) { func XTestDeadline(t testingT) {
t.Parallel()
c, _ := WithDeadline(Background(), time.Now().Add(shortDuration)) c, _ := WithDeadline(Background(), time.Now().Add(shortDuration))
if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
t.Errorf("c.String() = %q want prefix %q", got, prefix) t.Errorf("c.String() = %q want prefix %q", got, prefix)
@ -307,6 +310,8 @@ func XTestDeadline(t testingT) {
} }
func XTestTimeout(t testingT) { func XTestTimeout(t testingT) {
t.Parallel()
c, _ := WithTimeout(Background(), shortDuration) c, _ := WithTimeout(Background(), shortDuration)
if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
t.Errorf("c.String() = %q want prefix %q", got, prefix) t.Errorf("c.String() = %q want prefix %q", got, prefix)
@ -417,9 +422,9 @@ func XTestAllocs(t testingT, testingShort func() bool, testingAllocsPerRun func(
gccgoLimit: 3, gccgoLimit: 3,
}, },
{ {
desc: "WithTimeout(bg, 15*time.Millisecond)", desc: "WithTimeout(bg, 1*time.Nanosecond)",
f: func() { f: func() {
c, _ := WithTimeout(bg, 15*time.Millisecond) c, _ := WithTimeout(bg, 1*time.Nanosecond)
<-c.Done() <-c.Done()
}, },
limit: 12, limit: 12,
@ -545,7 +550,9 @@ func XTestLayersTimeout(t testingT) {
} }
func testLayers(t testingT, seed int64, testTimeout bool) { func testLayers(t testingT, seed int64, testTimeout bool) {
rand.Seed(seed) t.Parallel()
r := rand.New(rand.NewSource(seed))
errorf := func(format string, a ...interface{}) { errorf := func(format string, a ...interface{}) {
t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...) t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...)
} }
@ -560,7 +567,7 @@ func testLayers(t testingT, seed int64, testTimeout bool) {
ctx = Background() ctx = Background()
) )
for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ { for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ {
switch rand.Intn(3) { switch r.Intn(3) {
case 0: case 0:
v := new(value) v := new(value)
ctx = WithValue(ctx, v, v) ctx = WithValue(ctx, v, v)
@ -587,10 +594,12 @@ func testLayers(t testingT, seed int64, testTimeout bool) {
} }
} }
} }
select { if !testTimeout {
case <-ctx.Done(): select {
errorf("ctx should not be canceled yet") case <-ctx.Done():
default: errorf("ctx should not be canceled yet")
default:
}
} }
if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) { if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) {
t.Errorf("ctx.String() = %q want prefix %q", s, prefix) t.Errorf("ctx.String() = %q want prefix %q", s, prefix)
@ -608,7 +617,7 @@ func testLayers(t testingT, seed int64, testTimeout bool) {
} }
checkValues("after timeout") checkValues("after timeout")
} else { } else {
cancel := cancels[rand.Intn(len(cancels))] cancel := cancels[r.Intn(len(cancels))]
cancel() cancel()
select { select {
case <-ctx.Done(): case <-ctx.Done():

View File

@ -10,6 +10,8 @@ import (
"time" "time"
) )
const shortDuration = 1 * time.Millisecond // a reasonable duration to block in an example
// This example demonstrates the use of a cancelable context to prevent a // This example demonstrates the use of a cancelable context to prevent a
// goroutine leak. By the end of the example function, the goroutine started // goroutine leak. By the end of the example function, the goroutine started
// by gen will return without leaking. // by gen will return without leaking.
@ -55,7 +57,7 @@ func ExampleWithCancel() {
// This example passes a context with an arbitrary deadline to tell a blocking // This example passes a context with an arbitrary deadline to tell a blocking
// function that it should abandon its work as soon as it gets to it. // function that it should abandon its work as soon as it gets to it.
func ExampleWithDeadline() { func ExampleWithDeadline() {
d := time.Now().Add(50 * time.Millisecond) d := time.Now().Add(shortDuration)
ctx, cancel := context.WithDeadline(context.Background(), d) ctx, cancel := context.WithDeadline(context.Background(), d)
// Even though ctx will be expired, it is good practice to call its // Even though ctx will be expired, it is good practice to call its
@ -79,7 +81,7 @@ func ExampleWithDeadline() {
func ExampleWithTimeout() { func ExampleWithTimeout() {
// Pass a context with a timeout to tell a blocking function that it // Pass a context with a timeout to tell a blocking function that it
// should abandon its work after the timeout elapses. // should abandon its work after the timeout elapses.
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
defer cancel() defer cancel()
select { select {