diff options
Diffstat (limited to 'libgo/go/context')
-rw-r--r-- | libgo/go/context/context.go | 9 | ||||
-rw-r--r-- | libgo/go/context/context_test.go | 159 | ||||
-rw-r--r-- | libgo/go/context/example_test.go | 6 | ||||
-rw-r--r-- | libgo/go/context/x_test.go | 1 |
4 files changed, 122 insertions, 53 deletions
diff --git a/libgo/go/context/context.go b/libgo/go/context/context.go index b561968..b3fdb82 100644 --- a/libgo/go/context/context.go +++ b/libgo/go/context/context.go @@ -230,6 +230,9 @@ type CancelFunc func() // Canceling this context releases resources associated with it, so code should // call cancel as soon as the operations running in this Context complete. func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { + if parent == nil { + panic("cannot create context from nil parent") + } c := newCancelCtx(parent) propagateCancel(parent, &c) return &c, func() { c.cancel(true, Canceled) } @@ -425,6 +428,9 @@ func (c *cancelCtx) cancel(removeFromParent bool, err error) { // Canceling this context releases resources associated with it, so code should // call cancel as soon as the operations running in this Context complete. func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) { + if parent == nil { + panic("cannot create context from nil parent") + } if cur, ok := parent.Deadline(); ok && cur.Before(d) { // The current deadline is already sooner than the new one. return WithCancel(parent) @@ -511,6 +517,9 @@ func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { // struct{}. Alternatively, exported context key variables' static // type should be a pointer or interface. func WithValue(parent Context, key, val interface{}) Context { + if parent == nil { + panic("cannot create context from nil parent") + } if key == nil { panic("nil key") } diff --git a/libgo/go/context/context_test.go b/libgo/go/context/context_test.go index cff09fd..6b392a2 100644 --- a/libgo/go/context/context_test.go +++ b/libgo/go/context/context_test.go @@ -15,6 +15,7 @@ import ( ) type testingT interface { + Deadline() (time.Time, bool) Error(args ...interface{}) Errorf(format string, args ...interface{}) Fail() @@ -26,6 +27,7 @@ type testingT interface { Log(args ...interface{}) Logf(format string, args ...interface{}) Name() string + Parallel() Skip(args ...interface{}) SkipNow() Skipf(format string, args ...interface{}) @@ -39,6 +41,23 @@ type otherContext struct { Context } +const ( + shortDuration = 1 * time.Millisecond // a reasonable duration to block in a test + veryLongDuration = 1000 * time.Hour // an arbitrary upper bound on the test's running time +) + +// quiescent returns an arbitrary duration by which the program should have +// completed any remaining work and reached a steady (idle) state. +func quiescent(t testingT) time.Duration { + deadline, ok := t.Deadline() + if !ok { + return 5 * time.Second + } + + const arbitraryCleanupMargin = 1 * time.Second + return time.Until(deadline) - arbitraryCleanupMargin +} + func XTestBackground(t testingT) { c := Background() if c == nil { @@ -95,9 +114,7 @@ func XTestWithCancel(t testingT) { } } - cancel() - time.Sleep(100 * time.Millisecond) // let cancellation propagate - + cancel() // Should propagate synchronously. for i, c := range contexts { select { case <-c.Done(): @@ -123,7 +140,7 @@ func XTestParentFinishesChild(t testingT) { cancelChild, stop := WithCancel(parent) defer stop() valueChild := WithValue(parent, "key", "value") - timerChild, stop := WithTimeout(valueChild, 10000*time.Hour) + timerChild, stop := WithTimeout(valueChild, veryLongDuration) defer stop() select { @@ -252,11 +269,14 @@ func XTestChildFinishesFirst(t testingT) { } } -func testDeadline(c Context, name string, failAfter time.Duration, t testingT) { +func testDeadline(c Context, name string, t testingT) { t.Helper() + d := quiescent(t) + timer := time.NewTimer(d) + defer timer.Stop() select { - case <-time.After(failAfter): - t.Fatalf("%s: context should have timed out", name) + case <-timer.C: + t.Fatalf("%s: context not timed out after %v", name, d) case <-c.Done(): } if e := c.Err(); e != DeadlineExceeded { @@ -265,51 +285,54 @@ func testDeadline(c Context, name string, failAfter time.Duration, t testingT) { } func XTestDeadline(t testingT) { - c, _ := WithDeadline(Background(), time.Now().Add(50*time.Millisecond)) + t.Parallel() + + c, _ := WithDeadline(Background(), time.Now().Add(shortDuration)) if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { t.Errorf("c.String() = %q want prefix %q", got, prefix) } - testDeadline(c, "WithDeadline", time.Second, t) + testDeadline(c, "WithDeadline", t) - c, _ = WithDeadline(Background(), time.Now().Add(50*time.Millisecond)) + c, _ = WithDeadline(Background(), time.Now().Add(shortDuration)) o := otherContext{c} - testDeadline(o, "WithDeadline+otherContext", time.Second, t) + testDeadline(o, "WithDeadline+otherContext", t) - c, _ = WithDeadline(Background(), time.Now().Add(50*time.Millisecond)) + c, _ = WithDeadline(Background(), time.Now().Add(shortDuration)) o = otherContext{c} - c, _ = WithDeadline(o, time.Now().Add(4*time.Second)) - testDeadline(c, "WithDeadline+otherContext+WithDeadline", 2*time.Second, t) + c, _ = WithDeadline(o, time.Now().Add(veryLongDuration)) + testDeadline(c, "WithDeadline+otherContext+WithDeadline", t) - c, _ = WithDeadline(Background(), time.Now().Add(-time.Millisecond)) - testDeadline(c, "WithDeadline+inthepast", time.Second, t) + c, _ = WithDeadline(Background(), time.Now().Add(-shortDuration)) + testDeadline(c, "WithDeadline+inthepast", t) c, _ = WithDeadline(Background(), time.Now()) - testDeadline(c, "WithDeadline+now", time.Second, t) + testDeadline(c, "WithDeadline+now", t) } func XTestTimeout(t testingT) { - c, _ := WithTimeout(Background(), 50*time.Millisecond) + t.Parallel() + + c, _ := WithTimeout(Background(), shortDuration) if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) { t.Errorf("c.String() = %q want prefix %q", got, prefix) } - testDeadline(c, "WithTimeout", time.Second, t) + testDeadline(c, "WithTimeout", t) - c, _ = WithTimeout(Background(), 50*time.Millisecond) + c, _ = WithTimeout(Background(), shortDuration) o := otherContext{c} - testDeadline(o, "WithTimeout+otherContext", time.Second, t) + testDeadline(o, "WithTimeout+otherContext", t) - c, _ = WithTimeout(Background(), 50*time.Millisecond) + c, _ = WithTimeout(Background(), shortDuration) o = otherContext{c} - c, _ = WithTimeout(o, 3*time.Second) - testDeadline(c, "WithTimeout+otherContext+WithTimeout", 2*time.Second, t) + c, _ = WithTimeout(o, veryLongDuration) + testDeadline(c, "WithTimeout+otherContext+WithTimeout", t) } func XTestCanceledTimeout(t testingT) { c, _ := WithTimeout(Background(), time.Second) o := otherContext{c} - c, cancel := WithTimeout(o, 2*time.Second) - cancel() - time.Sleep(100 * time.Millisecond) // let cancellation propagate + c, cancel := WithTimeout(o, veryLongDuration) + cancel() // Should propagate synchronously. select { case <-c.Done(): default: @@ -399,9 +422,9 @@ func XTestAllocs(t testingT, testingShort func() bool, testingAllocsPerRun func( gccgoLimit: 3, }, { - desc: "WithTimeout(bg, 15*time.Millisecond)", + desc: "WithTimeout(bg, 1*time.Nanosecond)", f: func() { - c, _ := WithTimeout(bg, 15*time.Millisecond) + c, _ := WithTimeout(bg, 1*time.Nanosecond) <-c.Done() }, limit: 12, @@ -467,14 +490,20 @@ func XTestSimultaneousCancels(t testingT) { wg.Done() }(cancel) } + + d := quiescent(t) + stuck := make(chan struct{}) + timer := time.AfterFunc(d, func() { close(stuck) }) + defer timer.Stop() + // Wait on all the contexts in a random order. for ctx := range m { select { case <-ctx.Done(): - case <-time.After(1 * time.Second): + case <-stuck: buf := make([]byte, 10<<10) n := runtime.Stack(buf, true) - t.Fatalf("timed out waiting for <-ctx.Done(); stacks:\n%s", buf[:n]) + t.Fatalf("timed out after %v waiting for <-ctx.Done(); stacks:\n%s", d, buf[:n]) } } // Wait for all the cancel functions to return. @@ -485,10 +514,10 @@ func XTestSimultaneousCancels(t testingT) { }() select { case <-done: - case <-time.After(1 * time.Second): + case <-stuck: buf := make([]byte, 10<<10) n := runtime.Stack(buf, true) - t.Fatalf("timed out waiting for cancel functions; stacks:\n%s", buf[:n]) + t.Fatalf("timed out after %v waiting for cancel functions; stacks:\n%s", d, buf[:n]) } } @@ -500,12 +529,15 @@ func XTestInterlockedCancels(t testingT) { cancelChild() }() cancelParent() + d := quiescent(t) + timer := time.NewTimer(d) + defer timer.Stop() select { case <-child.Done(): - case <-time.After(1 * time.Second): + case <-timer.C: buf := make([]byte, 10<<10) n := runtime.Stack(buf, true) - t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", buf[:n]) + t.Fatalf("timed out after %v waiting for child.Done(); stacks:\n%s", d, buf[:n]) } } @@ -518,12 +550,13 @@ func XTestLayersTimeout(t testingT) { } 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{}) { t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...) } const ( - timeout = 200 * time.Millisecond minLayers = 30 ) type value int @@ -534,7 +567,7 @@ func testLayers(t testingT, seed int64, testTimeout bool) { ctx = Background() ) for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ { - switch rand.Intn(3) { + switch r.Intn(3) { case 0: v := new(value) ctx = WithValue(ctx, v, v) @@ -545,7 +578,11 @@ func testLayers(t testingT, seed int64, testTimeout bool) { cancels = append(cancels, cancel) case 2: var cancel CancelFunc - ctx, cancel = WithTimeout(ctx, timeout) + d := veryLongDuration + if testTimeout { + d = shortDuration + } + ctx, cancel = WithTimeout(ctx, d) cancels = append(cancels, cancel) numTimers++ } @@ -557,10 +594,12 @@ func testLayers(t testingT, seed int64, testTimeout bool) { } } } - select { - case <-ctx.Done(): - errorf("ctx should not be canceled yet") - default: + if !testTimeout { + select { + case <-ctx.Done(): + errorf("ctx should not be canceled yet") + default: + } } if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) { t.Errorf("ctx.String() = %q want prefix %q", s, prefix) @@ -568,14 +607,17 @@ func testLayers(t testingT, seed int64, testTimeout bool) { t.Log(ctx) checkValues("before cancel") if testTimeout { + d := quiescent(t) + timer := time.NewTimer(d) + defer timer.Stop() select { case <-ctx.Done(): - case <-time.After(timeout + time.Second): - errorf("ctx should have timed out") + case <-timer.C: + errorf("ctx should have timed out after %v", d) } checkValues("after timeout") } else { - cancel := cancels[rand.Intn(len(cancels))] + cancel := cancels[r.Intn(len(cancels))] cancel() select { case <-ctx.Done(): @@ -615,8 +657,8 @@ func XTestWithCancelCanceledParent(t testingT) { c, _ := WithCancel(parent) select { case <-c.Done(): - case <-time.After(5 * time.Second): - t.Fatal("timeout waiting for Done") + default: + t.Errorf("child not done immediately upon construction") } if got, want := c.Err(), Canceled; got != want { t.Errorf("child not cancelled; got = %v, want = %v", got, want) @@ -634,6 +676,21 @@ func XTestWithValueChecksKey(t testingT) { } } +func XTestInvalidDerivedFail(t testingT) { + panicVal := recoveredValue(func() { WithCancel(nil) }) + if panicVal == nil { + t.Error("expected panic") + } + panicVal = recoveredValue(func() { WithDeadline(nil, time.Now().Add(shortDuration)) }) + if panicVal == nil { + t.Error("expected panic") + } + panicVal = recoveredValue(func() { WithValue(nil, "foo", "bar") }) + if panicVal == nil { + t.Error("expected panic") + } +} + func recoveredValue(fn func()) (v interface{}) { defer func() { v = recover() }() fn() @@ -687,7 +744,7 @@ func XTestCustomContextGoroutines(t testingT) { cancel0() checkCreatedGoroutine() - _, cancel0 = WithTimeout(&myDoneCtx{Background()}, 1*time.Hour) + _, cancel0 = WithTimeout(&myDoneCtx{Background()}, veryLongDuration) cancel0() checkCreatedGoroutine() @@ -707,7 +764,7 @@ func XTestCustomContextGoroutines(t testingT) { defer cancel3b() checkCreatedGoroutine() // ctx1 is not providing Done, must not be used - ctx4, cancel4 := WithTimeout(ctx3, 1*time.Hour) + ctx4, cancel4 := WithTimeout(ctx3, veryLongDuration) defer cancel4() checkNoGoroutine() @@ -718,7 +775,7 @@ func XTestCustomContextGoroutines(t testingT) { cancel5() checkNoGoroutine() - _, cancel6 := WithTimeout(ctx5, 1*time.Hour) + _, cancel6 := WithTimeout(ctx5, veryLongDuration) defer cancel6() checkNoGoroutine() diff --git a/libgo/go/context/example_test.go b/libgo/go/context/example_test.go index b91a8ac..72ac5d2 100644 --- a/libgo/go/context/example_test.go +++ b/libgo/go/context/example_test.go @@ -10,6 +10,8 @@ import ( "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 // goroutine leak. By the end of the example function, the goroutine started // 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 // function that it should abandon its work as soon as it gets to it. func ExampleWithDeadline() { - d := time.Now().Add(50 * time.Millisecond) + d := time.Now().Add(shortDuration) ctx, cancel := context.WithDeadline(context.Background(), d) // Even though ctx will be expired, it is good practice to call its @@ -79,7 +81,7 @@ func ExampleWithDeadline() { func ExampleWithTimeout() { // Pass a context with a timeout to tell a blocking function that it // 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() select { diff --git a/libgo/go/context/x_test.go b/libgo/go/context/x_test.go index e85ef2d..00eca72 100644 --- a/libgo/go/context/x_test.go +++ b/libgo/go/context/x_test.go @@ -26,5 +26,6 @@ func TestLayersTimeout(t *testing.T) { XTestLayersTimeout(t) } func TestCancelRemoves(t *testing.T) { XTestCancelRemoves(t) } func TestWithCancelCanceledParent(t *testing.T) { XTestWithCancelCanceledParent(t) } func TestWithValueChecksKey(t *testing.T) { XTestWithValueChecksKey(t) } +func TestInvalidDerivedFail(t *testing.T) { XTestInvalidDerivedFail(t) } func TestDeadlineExceededSupportsTimeout(t *testing.T) { XTestDeadlineExceededSupportsTimeout(t) } func TestCustomContextGoroutines(t *testing.T) { XTestCustomContextGoroutines(t) } |