Преглед изворни кода

go.net/context: define the Context type, which supports propagating
deadlines, cancellation, and other values across APIs and between
processes.

LGTM=crawshaw
R=rsc, crawshaw
CC=bradfitz, golang-codereviews
https://golang.org/cl/99330045

Sameer Ajmani пре 11 година
родитељ
комит
f572974747
3 измењених фајлова са 800 додато и 0 уклоњено
  1. 357 0
      context/context.go
  2. 417 0
      context/context_test.go
  3. 26 0
      context/withtimeout_test.go

+ 357 - 0
context/context.go

@@ -0,0 +1,357 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package context defines the Context type, which carries deadlines, cancellation
+// signals, and other request-scoped values across API boundaries and between
+// processes.
+//
+// Incoming requests to a server establish a Context, and outgoing calls to servers
+// should accept a Context.  The chain of function calls between must propagate the
+// Context, optionally replacing it with a modified copy created using
+// WithDeadline, WithTimeout, WithCancel, or WithValue.
+//
+// Functions that require a Context should take it as the first parameter, named ctx:
+//
+//   func DoSomething(ctx context.Context, arg Arg) error {
+//     // ... use ctx ...
+//   }
+//
+// Do not pass a nil Context, even if a function permits it.  Pass context.TODO
+// if you are unsure about which Context to use.
+//
+// Future packages will create Contexts from standard request types like
+// *http.Request.
+package context
+
+import (
+	"errors"
+	"fmt"
+	"sync"
+	"time"
+)
+
+// An Key identifies a specific Value in a Context.  Functions that wish to
+// store Values in Context typically allocate a Key in a global variable then
+// use that Key as the argument to context.WithValue and Context.Value.
+//
+// Packages that define a Context Key should provide type-safe accessors for the
+// Values stores using that Key:
+//
+//   // package foo defines a value that's stored in Contexts.
+//   package foo
+//
+//   import "code.google.com/p/go.net/context"
+//
+//   // Foo is the type of value stored in the Contexts.
+//   type Foo struct {...}
+//
+//   // contextKey is the Key for foo.Foo values in Contexts.  It is
+//   // unexported; foo clients use foo.NewContext and foo.FromContext
+//   // instead of using this Key directly.
+//   var contextKey = context.NewKey("import/path/of/foo.Foo")
+//
+//   // NewContext returns a new Context that carries value foo.
+//   func NewContext(ctx context.Context, foo *Foo) context.Context {
+//     return context.WithValue(contextKey, foo)
+//   }
+//
+//   // FromContext returns the Foo value stored in ctx, if any.
+//   func FromContext(ctx context.Context) (*Foo, bool) {
+//     foo, ok := ctx.Value(contextKey).(*Foo)
+//     return foo, ok
+//   }
+type Key struct {
+	name string
+}
+
+var keys = make(map[string]Key)
+
+// NewKey allocates a new Key with the provided name.  The name must be
+// non-empty and globally unique: if NewKey is called multiple times with the
+// same name it panics.  NewKey should only be called during initalization.
+func NewKey(name string) Key {
+	if name == "" {
+		panic("context.NewKey called with an empty name")
+	}
+	if _, ok := keys[name]; ok {
+		panic(fmt.Sprintf("context.NewKey(%q) called multiple times", name))
+	}
+	k := Key{name}
+	keys[name] = k
+	return k
+}
+
+// String returns the Key's name.
+func (k Key) String() string {
+	return k.name
+}
+
+// A Context carries deadlines, and cancellation signals, and other values
+// across API boundaries.
+//
+// Context's methods may be called by multiple goroutines simultaneously.
+type Context interface {
+	// Deadline returns the time when work done on behalf of this context
+	// should be canceled.  Deadline returns ok==false when no deadline is
+	// set.  Successive calls to Deadline return the same results.
+	Deadline() (deadline time.Time, ok bool)
+
+	// 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 become done.  Successive calls to Done return the same value.
+	//
+	// WithCancel arranges for Done to be closed when cancel is called;
+	// WithDeadline arranges for Done to be closed when the deadline
+	// expires; WithTimeout arranges for Done to be closed when the timeout
+	// elapses.
+	//
+	// Done is provided for use in select statements:
+	//
+	//   // DoSomething calls DoSomethingSlow and returns as soon as
+	//   // it returns or ctx.Done is closed.
+	//   func DoSomething(ctx context.Context) (Result, error) {
+	//     c := make(chan Result, 1)
+	//     go func() { c <- DoSomethingSlow(ctx) }()
+	//     select {
+	//     case res := <-c:
+	//       return res, nil
+	//     case <-ctx.Done():
+	//       return nil, ctx.Err()
+	//     }
+	//   }
+	Done() <-chan struct{}
+
+	// Err returns a non-nil error value after Done is closed.  Err returns
+	// Canceled if the context was canceled; Err returns DeadlineExceeded if
+	// the context's deadline passed.  No other values for Err are defined.
+	Err() error
+
+	// Value returns the value associated with this context for key, or nil
+	// if no value is associated with key.  Successive calls to Value with
+	// the same key returns the same result.
+	Value(key Key) interface{}
+}
+
+// Canceled is the error returned by Context.Err when the context is canceled.
+var Canceled = errors.New("context canceled")
+
+// DeadlineExceeded is the error returned by Context.Err when the context's
+// deadline passes.
+var DeadlineExceeded = errors.New("context deadline exceeded")
+
+// A ctx is a Context that automatically propagates cancellation signals to
+// other ctxs (those created using this ctx as their parent).  A ctx also
+// manages its own deadline timer.
+type ctx struct {
+	parent Context       // set by newCtx
+	done   chan struct{} // closed by the first cancel call.  nil if uncancelable.
+
+	key Key         // set by WithValue
+	val interface{} // set by WithValue
+
+	deadline    time.Time // set by WithDeadline
+	deadlineSet bool      // set by WithDeadline
+
+	// parent.mu ACQUIRED_BEFORE mu: mu must not be held when acquiring parent.mu.
+	mu       sync.RWMutex
+	children map[*ctx]bool // set to nil by the first cancel call
+	err      error         // set to non-nil by the first cancel call
+	timer    *time.Timer   // set by WithDeadline, read by cancel
+}
+
+func (c *ctx) Deadline() (deadline time.Time, ok bool) {
+	return c.deadline, c.deadlineSet
+}
+
+func (c *ctx) Done() <-chan struct{} {
+	return c.done // may be nil
+}
+
+func (c *ctx) Err() error {
+	c.mu.RLock() // c.err is under mu
+	defer c.mu.RUnlock()
+	return c.err
+}
+
+func (c *ctx) Value(key Key) interface{} {
+	if c.key == key {
+		return c.val
+	}
+	if c.parent != nil {
+		return c.parent.Value(key)
+	}
+	return nil
+}
+
+// The background context for this process.
+var background = newCtx(nil, neverCanceled)
+
+// Background returns an ambient background context, which is never nil. This
+// context represents the intrinsic state of the application at startup time,
+// independent of any incoming request state.
+//
+// The Background context is typically only used by the main function and tests.
+func Background() Context {
+	return background
+}
+
+// TODO returns an ambient background context, which is never nil.  Code should
+// use context.TODO when it's unclear which Context to use or it's is not yet
+// available (because the surrounding function has not yet been extended to
+// accept a Context parameter).  TODO is recognized by static analysis tools
+// that determine whether Contexts are propagated correctly in a program.
+func TODO() Context {
+	return Background()
+}
+
+// A CancelFunc tells an operation to abandon its work.
+// A CancelFunc does not wait for the work to stop.
+// After the first, subsequent calls to a CancelFunc do nothing.
+type CancelFunc func()
+
+// WithCancel returns a copy of parent with a new Done channel. The returned
+// context's Done channel is closed when the returned cancel function is called
+// or when the parent context's Done channel is closed, whichever happens first.
+func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
+	return withCancel(parent)
+}
+
+func withCancel(parent Context) (*ctx, CancelFunc) {
+	c := newCtx(parent, maybeCanceled)
+	return c, func() { c.cancel(true, Canceled) }
+}
+
+// WithDeadline returns a copy of the parent context with the deadline adjusted
+// to be no later than d.  If the parent's deadline is already earlier than d,
+// WithDeadline(parent, d) is semantically equivalent to parent.  The returned
+// context's Done channel is closed when the deadline expires, when the returned
+// cancel function is called, or when the parent context's Done channel is
+// closed, whichever happens first.
+//
+// Cancelling this context releases resources associated with the deadline
+// timer, so code should call cancel as soon as the operations running in this
+// Context complete.
+func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
+	c, cancel := withCancel(parent)
+	if cur, ok := c.Deadline(); ok && cur.Before(deadline) {
+		// The current deadline is already sooner than the new one.
+		return c, cancel
+	}
+	c.deadline, c.deadlineSet = deadline, true
+	d := deadline.Sub(time.Now())
+	if d <= 0 {
+		// TODO(sameer): pass removeFromParent=true here?
+		c.cancel(false, DeadlineExceeded) // deadline has already passed
+		return c, cancel
+	}
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	c.timer = time.AfterFunc(d, func() {
+		// TODO(sameer): pass removeFromParent=true here?
+		c.cancel(false, DeadlineExceeded)
+	})
+	return c, cancel
+}
+
+// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
+//
+// Cancelling this context releases resources associated with the deadline
+// timer, so code should call cancel as soon as the operations running in this
+// Context complete:
+//
+//   func slowOperationWithTimeout(ctx context.Context) (Result, error) {
+//     ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
+//     defer cancel()  // releases resources if slowOperation completes before timeout elapses
+//     return slowOperation(ctx)
+//   }
+func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
+	return WithDeadline(parent, time.Now().Add(timeout))
+}
+
+// WithValue returns a copy of parent in which the value associated with k is v.
+//
+// Use context Values only for request-scoped data that transits processes and
+// APIs, not for passing optional parameters to functions.
+func WithValue(parent Context, k Key, v interface{}) Context {
+	c := newCtx(parent, neverCanceled)
+	c.key, c.val = k, v
+	return c
+}
+
+const maybeCanceled = true
+const neverCanceled = false
+
+func newCtx(parent Context, childMayCancel bool) *ctx {
+	c := &ctx{parent: parent}
+	parentMayCancel := parent != nil && parent.Done() != nil
+	if childMayCancel || parentMayCancel {
+		c.done = make(chan struct{})
+	}
+	if parent != nil {
+		c.deadline, c.deadlineSet = parent.Deadline()
+	}
+	if parentMayCancel {
+		if p, ok := parent.(*ctx); ok {
+			// Arrange for the new ctx to be canceled when the parent is.
+			p.mu.Lock()
+			if p.err != nil {
+				// parent has already been canceled
+				c.cancel(false, p.err)
+			} else {
+				if p.children == nil {
+					p.children = make(map[*ctx]bool)
+				}
+				p.children[c] = true
+			}
+			p.mu.Unlock()
+		} else {
+			// Cancel the new ctx when context.Done is closed.
+			go func() {
+				select {
+				case <-parent.Done():
+					c.cancel(false, parent.Err())
+				case <-c.done:
+				}
+			}()
+		}
+	}
+	return c
+}
+
+// cancel closes c.done, cancels each of this ctx's children, and, if
+// removeFromParent is true, removes this ctx from its parent's children.
+// cancel stops c.timer, if it is running.
+func (c *ctx) cancel(removeFromParent bool, err error) {
+	if err == nil {
+		panic("context: internal error: missing cancel error")
+	}
+	c.mu.Lock()
+	if c.done == nil {
+		panic("context: internal error: missing done channel")
+	}
+	if c.err != nil {
+		c.mu.Unlock()
+		return // already canceled
+	}
+	if c.timer != nil {
+		c.timer.Stop()
+		c.timer = nil
+	}
+	close(c.done)
+	c.err = err
+	for child := range c.children {
+		// NOTE: acquiring the child's lock while holding parent's lock.
+		child.cancel(false, err)
+	}
+	c.children = nil
+	c.mu.Unlock()
+
+	if p, ok := c.parent.(*ctx); ok && p != nil && removeFromParent {
+		p.mu.Lock()
+		if p.children != nil {
+			delete(p.children, c)
+		}
+		p.mu.Unlock()
+	}
+}

+ 417 - 0
context/context_test.go

@@ -0,0 +1,417 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package context
+
+import (
+	"fmt"
+	"runtime"
+	"sync"
+	"testing"
+	"time"
+)
+
+// otherContext is a Context that's not a *ctx.  This lets us test code paths
+// that differ based on the underlying type of the Context.
+type otherContext struct {
+	Context
+}
+
+func TestBackground(t *testing.T) {
+	c := Background()
+	if c == nil {
+		t.Fatalf("Background returned nil")
+	}
+	select {
+	case x := <-c.Done():
+		t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
+	default:
+	}
+}
+
+func TestWithCancel(t *testing.T) {
+	c1, cancel := WithCancel(Background())
+	o := otherContext{c1}
+	c2 := newCtx(o, maybeCanceled)
+	contexts := []Context{c1, o, c2}
+
+	for i, c := range contexts {
+		if d := c.Done(); d == nil {
+			t.Errorf("c[%d].Done() == %v want non-nil", i, d)
+		}
+		if e := c.Err(); e != nil {
+			t.Errorf("c[%d].Err() == %v want nil", i, e)
+		}
+
+		select {
+		case x := <-c.Done():
+			t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
+		default:
+		}
+	}
+
+	cancel()
+	time.Sleep(100 * time.Millisecond) // let cancellation propagate
+
+	for i, c := range contexts {
+		select {
+		case <-c.Done():
+		default:
+			t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i)
+		}
+		if e := c.Err(); e != Canceled {
+			t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled)
+		}
+	}
+}
+
+func TestParentFinishesChild(t *testing.T) {
+	parent, cancel := WithCancel(Background())
+	pctx := parent.(*ctx)
+	child1 := newCtx(parent, maybeCanceled)
+	child2 := newCtx(parent, neverCanceled)
+
+	select {
+	case x := <-parent.Done():
+		t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
+	case x := <-child1.Done():
+		t.Errorf("<-child1.Done() == %v want nothing (it should block)", x)
+	case x := <-child2.Done():
+		t.Errorf("<-child2.Done() == %v want nothing (it should block)", x)
+	default:
+	}
+
+	pctx.mu.Lock()
+	if len(pctx.children) != 2 ||
+		!pctx.children[child1] || child1.parent != pctx ||
+		!pctx.children[child2] || child2.parent != pctx {
+		t.Errorf("bad linkage: pctx.children = %v, child1.parent = %v, child2.parent = %v",
+			pctx.children, child1.parent, child2.parent)
+	}
+	pctx.mu.Unlock()
+
+	cancel()
+
+	pctx.mu.Lock()
+	if len(pctx.children) != 0 {
+		t.Errorf("pctx.cancel didn't clear pctx.children = %v", pctx.children)
+	}
+	pctx.mu.Unlock()
+
+	// parent and children should all be finished.
+	select {
+	case <-parent.Done():
+	default:
+		t.Errorf("<-parent.Done() blocked, but shouldn't have")
+	}
+	if e := parent.Err(); e != Canceled {
+		t.Errorf("parent.Err() == %v want %v", e, Canceled)
+	}
+	select {
+	case <-child1.Done():
+	default:
+		t.Errorf("<-child1.Done() blocked, but shouldn't have")
+	}
+	if e := child1.Err(); e != Canceled {
+		t.Errorf("child1.Err() == %v want %v", e, Canceled)
+	}
+	select {
+	case <-child2.Done():
+	default:
+		t.Errorf("<-child2.Done() blocked, but shouldn't have")
+	}
+	if e := child2.Err(); e != Canceled {
+		t.Errorf("child2.Err() == %v want %v", e, Canceled)
+	}
+
+	// New should return a canceled context on a canceled parent.
+	child3 := newCtx(parent, neverCanceled)
+	select {
+	case <-child3.Done():
+	default:
+		t.Errorf("<-child3.Done() blocked, but shouldn't have")
+	}
+	if e := child3.Err(); e != Canceled {
+		t.Errorf("child3.Err() == %v want %v", e, Canceled)
+	}
+}
+
+func TestChildFinishesFirst(t *testing.T) {
+	for _, parentMayCancel := range []bool{neverCanceled, maybeCanceled} {
+		parent := newCtx(nil, parentMayCancel)
+		child, cancel := WithCancel(parent)
+		pctx := parent
+		cctx := child.(*ctx)
+
+		select {
+		case x := <-parent.Done():
+			t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
+		case x := <-child.Done():
+			t.Errorf("<-child.Done() == %v want nothing (it should block)", x)
+		default:
+		}
+
+		if cctx.parent != pctx {
+			t.Errorf("bad linkage: cctx.parent = %v, parent = %v", cctx.parent, pctx)
+		}
+
+		if parentMayCancel {
+			pctx.mu.Lock()
+			if len(pctx.children) != 1 || !pctx.children[cctx] {
+				t.Errorf("bad linkage: pctx.children = %v, cctx = %v", pctx.children, cctx)
+			}
+			pctx.mu.Unlock()
+		}
+
+		cancel()
+
+		pctx.mu.Lock()
+		if len(pctx.children) != 0 {
+			t.Errorf("child.Cancel didn't remove self from pctx.children = %v", pctx.children)
+		}
+		pctx.mu.Unlock()
+
+		// child should be finished.
+		select {
+		case <-child.Done():
+		default:
+			t.Errorf("<-child.Done() blocked, but shouldn't have")
+		}
+		if e := child.Err(); e != Canceled {
+			t.Errorf("child.Err() == %v want %v", e, Canceled)
+		}
+
+		// parent should not be finished.
+		select {
+		case x := <-parent.Done():
+			t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
+		default:
+		}
+		if e := parent.Err(); e != nil {
+			t.Errorf("parent.Err() == %v want nil", e)
+		}
+	}
+}
+
+func testDeadline(c Context, wait time.Duration, t *testing.T) {
+	select {
+	case <-time.After(wait):
+		t.Fatalf("context should have timed out")
+	case <-c.Done():
+	}
+	if e := c.Err(); e != DeadlineExceeded {
+		t.Errorf("c.Err() == %v want %v", e, DeadlineExceeded)
+	}
+}
+
+func TestDeadline(t *testing.T) {
+	c, _ := WithDeadline(nil, time.Now().Add(100*time.Millisecond))
+	testDeadline(c, 200*time.Millisecond, t)
+
+	c, _ = WithDeadline(nil, time.Now().Add(100*time.Millisecond))
+	o := otherContext{c}
+	testDeadline(o, 200*time.Millisecond, t)
+
+	c, _ = WithDeadline(nil, time.Now().Add(100*time.Millisecond))
+	o = otherContext{c}
+	c, _ = WithDeadline(o, time.Now().Add(300*time.Millisecond))
+	testDeadline(c, 200*time.Millisecond, t)
+}
+
+func TestTimeout(t *testing.T) {
+	c, _ := WithTimeout(nil, 100*time.Millisecond)
+	testDeadline(c, 200*time.Millisecond, t)
+
+	c, _ = WithTimeout(nil, 100*time.Millisecond)
+	o := otherContext{c}
+	testDeadline(o, 200*time.Millisecond, t)
+
+	c, _ = WithTimeout(nil, 100*time.Millisecond)
+	o = otherContext{c}
+	c, _ = WithTimeout(o, 300*time.Millisecond)
+	testDeadline(c, 200*time.Millisecond, t)
+}
+
+func TestCancelledTimeout(t *testing.T) {
+	c, _ := WithTimeout(nil, 200*time.Millisecond)
+	o := otherContext{c}
+	c, cancel := WithTimeout(o, 400*time.Millisecond)
+	cancel()
+	time.Sleep(100 * time.Millisecond) // let cancellation propagate
+	select {
+	case <-c.Done():
+	default:
+		t.Errorf("<-c.Done() blocked, but shouldn't have")
+	}
+	if e := c.Err(); e != Canceled {
+		t.Errorf("c.Err() == %v want %v", e, Canceled)
+	}
+}
+
+var k1, k2 = NewKey("k1"), NewKey("k2")
+
+func TestValues(t *testing.T) {
+	check := func(c Context, nm, v1, v2 string) {
+		if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 {
+			t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0)
+		}
+		if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 {
+			t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0)
+		}
+	}
+
+	c0 := Background()
+	check(c0, "c0", "", "")
+
+	c1 := WithValue(nil, k1, "c1k1")
+	check(c1, "c1", "c1k1", "")
+
+	c2 := WithValue(c1, k2, "c2k2")
+	check(c2, "c2", "c1k1", "c2k2")
+
+	c3 := WithValue(c2, k1, nil)
+	check(c3, "c3", "", "c2k2")
+
+	o0 := otherContext{Background()}
+	check(o0, "o0", "", "")
+
+	o1 := otherContext{WithValue(nil, k1, "c1k1")}
+	check(o1, "o1", "c1k1", "")
+
+	o2 := WithValue(o1, k2, "o2k2")
+	check(o2, "o2", "c1k1", "o2k2")
+
+	o3 := otherContext{c3}
+	check(o3, "o3", "", "c2k2")
+}
+
+func TestAllocs(t *testing.T) {
+	bg := Background()
+	for _, test := range []struct {
+		desc       string
+		f          func()
+		limit      float64
+		gccgoLimit float64
+	}{
+		{
+			desc:       "Background()",
+			f:          func() { Background() },
+			limit:      0,
+			gccgoLimit: 0,
+		},
+		{
+			desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1),
+			f: func() {
+				c := WithValue(bg, k1, nil)
+				c.Value(k1)
+			},
+			limit:      3,
+			gccgoLimit: 3,
+		},
+		{
+			desc: "WithTimeout(bg, 1*time.Nanosecond)",
+			f: func() {
+				c, _ := WithTimeout(bg, 1*time.Nanosecond)
+				<-c.Done()
+			},
+			limit:      7,
+			gccgoLimit: 13,
+		},
+		{
+			desc: "WithCancel(bg)",
+			f: func() {
+				c, cancel := WithCancel(bg)
+				cancel()
+				<-c.Done()
+			},
+			limit:      7,
+			gccgoLimit: 8,
+		},
+		{
+			desc: "WithTimeout(bg, 100*time.Millisecond)",
+			f: func() {
+				c, cancel := WithTimeout(bg, 100*time.Millisecond)
+				cancel()
+				<-c.Done()
+			},
+			limit:      16,
+			gccgoLimit: 25,
+		},
+	} {
+		limit := test.limit
+		if runtime.Compiler == "gccgo" {
+			// gccgo does not yet do escape analysis.
+			// TOOD(iant): Remove this when gccgo does do escape analysis.
+			limit = test.gccgoLimit
+		}
+		if n := testing.AllocsPerRun(1000, test.f); n > limit {
+			t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit))
+		}
+	}
+}
+
+func TestSimultaneousCancels(t *testing.T) {
+	root, cancel := WithCancel(Background())
+	m := map[Context]CancelFunc{root: cancel}
+	q := []Context{root}
+	// Create a tree of contexts.
+	for len(q) != 0 && len(m) < 100 {
+		parent := q[0]
+		q = q[1:]
+		for i := 0; i < 4; i++ {
+			ctx, cancel := WithCancel(parent)
+			m[ctx] = cancel
+			q = append(q, ctx)
+		}
+	}
+	// Start all the cancels in a random order.
+	var wg sync.WaitGroup
+	wg.Add(len(m))
+	for _, cancel := range m {
+		go func(cancel CancelFunc) {
+			cancel()
+			wg.Done()
+		}(cancel)
+	}
+	// Wait on all the contexts in a random order.
+	for ctx := range m {
+		select {
+		case <-ctx.Done():
+		case <-time.After(1 * time.Second):
+			buf := make([]byte, 10<<10)
+			n := runtime.Stack(buf, true)
+			t.Fatalf("timed out waiting for <-ctx.Done(); stacks:\n%s", buf[:n])
+		}
+	}
+	// Wait for all the cancel functions to return.
+	done := make(chan struct{})
+	go func() {
+		wg.Wait()
+		close(done)
+	}()
+	select {
+	case <-done:
+	case <-time.After(1 * time.Second):
+		buf := make([]byte, 10<<10)
+		n := runtime.Stack(buf, true)
+		t.Fatalf("timed out waiting for cancel functions; stacks:\n%s", buf[:n])
+	}
+}
+
+func TestInterlockedCancels(t *testing.T) {
+	parent, cancelParent := WithCancel(Background())
+	child, cancelChild := WithCancel(parent)
+	go func() {
+		parent.Done()
+		cancelChild()
+	}()
+	cancelParent()
+	select {
+	case <-child.Done():
+	case <-time.After(1 * time.Second):
+		buf := make([]byte, 10<<10)
+		n := runtime.Stack(buf, true)
+		t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", buf[:n])
+	}
+}

+ 26 - 0
context/withtimeout_test.go

@@ -0,0 +1,26 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package context_test
+
+import (
+	"fmt"
+	"time"
+
+	"code.google.com/p/go.net/context"
+)
+
+func ExampleWithTimeout() {
+	// Pass a context with a timeout to tell a blocking function that it
+	// should abandon its work after the timeout elapses.
+	ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond)
+	select {
+	case <-time.After(200 * time.Millisecond):
+		fmt.Println("overslept")
+	case <-ctx.Done():
+		fmt.Println(ctx.Err()) // prints "context deadline exceeded"
+	}
+	// Output:
+	// context deadline exceeded
+}