aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/context
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/context')
-rw-r--r--libgo/go/context/context.go9
-rw-r--r--libgo/go/context/context_test.go159
-rw-r--r--libgo/go/context/example_test.go6
-rw-r--r--libgo/go/context/x_test.go1
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) }