diff options
author | Ian Lance Taylor <iant@golang.org> | 2020-01-02 15:05:27 -0800 |
---|---|---|
committer | Ian Lance Taylor <iant@golang.org> | 2020-01-21 23:53:22 -0800 |
commit | 5a8ea165926cb0737ab03bc48c18dc5198ab5305 (patch) | |
tree | 962dc3357c57f019f85658f99e2e753e30201c27 /libgo/go/context | |
parent | 6ac6529e155c9baa0aaaed7aca06bd38ebda5b43 (diff) | |
download | gcc-5a8ea165926cb0737ab03bc48c18dc5198ab5305.zip gcc-5a8ea165926cb0737ab03bc48c18dc5198ab5305.tar.gz gcc-5a8ea165926cb0737ab03bc48c18dc5198ab5305.tar.bz2 |
libgo: update to Go1.14beta1
Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/214297
Diffstat (limited to 'libgo/go/context')
-rw-r--r-- | libgo/go/context/context.go | 63 | ||||
-rw-r--r-- | libgo/go/context/context_test.go | 85 | ||||
-rw-r--r-- | libgo/go/context/x_test.go | 1 |
3 files changed, 132 insertions, 17 deletions
diff --git a/libgo/go/context/context.go b/libgo/go/context/context.go index 6259085..b561968 100644 --- a/libgo/go/context/context.go +++ b/libgo/go/context/context.go @@ -51,6 +51,7 @@ import ( "errors" "internal/reflectlite" "sync" + "sync/atomic" "time" ) @@ -67,6 +68,8 @@ type Context interface { // Done returns a channel that's closed when work done on behalf of this // context should be canceled. Done may return nil if this context can // never be canceled. Successive calls to Done return the same value. + // The close of the Done channel may happen asynchronously, + // after the cancel function returns. // // WithCancel arranges for Done to be closed when cancel is called; // WithDeadline arranges for Done to be closed when the deadline @@ -237,11 +240,24 @@ func newCancelCtx(parent Context) cancelCtx { return cancelCtx{Context: parent} } +// goroutines counts the number of goroutines ever created; for testing. +var goroutines int32 + // propagateCancel arranges for child to be canceled when parent is. func propagateCancel(parent Context, child canceler) { - if parent.Done() == nil { + done := parent.Done() + if done == nil { return // parent is never canceled } + + select { + case <-done: + // parent is already canceled + child.cancel(false, parent.Err()) + return + default: + } + if p, ok := parentCancelCtx(parent); ok { p.mu.Lock() if p.err != nil { @@ -255,6 +271,7 @@ func propagateCancel(parent Context, child canceler) { } p.mu.Unlock() } else { + atomic.AddInt32(&goroutines, +1) go func() { select { case <-parent.Done(): @@ -265,22 +282,31 @@ func propagateCancel(parent Context, child canceler) { } } -// parentCancelCtx follows a chain of parent references until it finds a -// *cancelCtx. This function understands how each of the concrete types in this -// package represents its parent. +// &cancelCtxKey is the key that a cancelCtx returns itself for. +var cancelCtxKey int + +// parentCancelCtx returns the underlying *cancelCtx for parent. +// It does this by looking up parent.Value(&cancelCtxKey) to find +// the innermost enclosing *cancelCtx and then checking whether +// parent.Done() matches that *cancelCtx. (If not, the *cancelCtx +// has been wrapped in a custom implementation providing a +// different done channel, in which case we should not bypass it.) func parentCancelCtx(parent Context) (*cancelCtx, bool) { - for { - switch c := parent.(type) { - case *cancelCtx: - return c, true - case *timerCtx: - return &c.cancelCtx, true - case *valueCtx: - parent = c.Context - default: - return nil, false - } + done := parent.Done() + if done == closedchan || done == nil { + return nil, false + } + p, ok := parent.Value(&cancelCtxKey).(*cancelCtx) + if !ok { + return nil, false } + p.mu.Lock() + ok = p.done == done + p.mu.Unlock() + if !ok { + return nil, false + } + return p, true } // removeChild removes a context from its parent. @@ -321,6 +347,13 @@ type cancelCtx struct { err error // set to non-nil by the first cancel call } +func (c *cancelCtx) Value(key interface{}) interface{} { + if key == &cancelCtxKey { + return c + } + return c.Context.Value(key) +} + func (c *cancelCtx) Done() <-chan struct{} { c.mu.Lock() if c.done == nil { diff --git a/libgo/go/context/context_test.go b/libgo/go/context/context_test.go index b07a5cf..cff09fd 100644 --- a/libgo/go/context/context_test.go +++ b/libgo/go/context/context_test.go @@ -10,6 +10,7 @@ import ( "runtime" "strings" "sync" + "sync/atomic" "time" ) @@ -21,6 +22,7 @@ type testingT interface { Failed() bool Fatal(args ...interface{}) Fatalf(format string, args ...interface{}) + Helper() Log(args ...interface{}) Logf(format string, args ...interface{}) Name() string @@ -251,6 +253,7 @@ func XTestChildFinishesFirst(t testingT) { } func testDeadline(c Context, name string, failAfter time.Duration, t testingT) { + t.Helper() select { case <-time.After(failAfter): t.Fatalf("%s: context should have timed out", name) @@ -401,8 +404,8 @@ func XTestAllocs(t testingT, testingShort func() bool, testingAllocsPerRun func( c, _ := WithTimeout(bg, 15*time.Millisecond) <-c.Done() }, - limit: 8, - gccgoLimit: 18, + limit: 12, + gccgoLimit: 15, }, { desc: "WithCancel(bg)", @@ -648,3 +651,81 @@ func XTestDeadlineExceededSupportsTimeout(t testingT) { t.Fatal("wrong value for timeout") } } + +type myCtx struct { + Context +} + +type myDoneCtx struct { + Context +} + +func (d *myDoneCtx) Done() <-chan struct{} { + c := make(chan struct{}) + return c +} + +func XTestCustomContextGoroutines(t testingT) { + g := atomic.LoadInt32(&goroutines) + checkNoGoroutine := func() { + t.Helper() + now := atomic.LoadInt32(&goroutines) + if now != g { + t.Fatalf("%d goroutines created", now-g) + } + } + checkCreatedGoroutine := func() { + t.Helper() + now := atomic.LoadInt32(&goroutines) + if now != g+1 { + t.Fatalf("%d goroutines created, want 1", now-g) + } + g = now + } + + _, cancel0 := WithCancel(&myDoneCtx{Background()}) + cancel0() + checkCreatedGoroutine() + + _, cancel0 = WithTimeout(&myDoneCtx{Background()}, 1*time.Hour) + cancel0() + checkCreatedGoroutine() + + checkNoGoroutine() + defer checkNoGoroutine() + + ctx1, cancel1 := WithCancel(Background()) + defer cancel1() + checkNoGoroutine() + + ctx2 := &myCtx{ctx1} + ctx3, cancel3 := WithCancel(ctx2) + defer cancel3() + checkNoGoroutine() + + _, cancel3b := WithCancel(&myDoneCtx{ctx2}) + defer cancel3b() + checkCreatedGoroutine() // ctx1 is not providing Done, must not be used + + ctx4, cancel4 := WithTimeout(ctx3, 1*time.Hour) + defer cancel4() + checkNoGoroutine() + + ctx5, cancel5 := WithCancel(ctx4) + defer cancel5() + checkNoGoroutine() + + cancel5() + checkNoGoroutine() + + _, cancel6 := WithTimeout(ctx5, 1*time.Hour) + defer cancel6() + checkNoGoroutine() + + // Check applied to cancelled context. + cancel6() + cancel1() + _, cancel7 := WithCancel(ctx5) + defer cancel7() + checkNoGoroutine() +} diff --git a/libgo/go/context/x_test.go b/libgo/go/context/x_test.go index d14b6f1..e85ef2d 100644 --- a/libgo/go/context/x_test.go +++ b/libgo/go/context/x_test.go @@ -27,3 +27,4 @@ func TestCancelRemoves(t *testing.T) { XTestCancelRemoves(t) } func TestWithCancelCanceledParent(t *testing.T) { XTestWithCancelCanceledParent(t) } func TestWithValueChecksKey(t *testing.T) { XTestWithValueChecksKey(t) } func TestDeadlineExceededSupportsTimeout(t *testing.T) { XTestDeadlineExceededSupportsTimeout(t) } +func TestCustomContextGoroutines(t *testing.T) { XTestCustomContextGoroutines(t) } |