diff options
Diffstat (limited to 'libgo/go/context/context.go')
-rw-r--r-- | libgo/go/context/context.go | 63 |
1 files changed, 48 insertions, 15 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 { |