Commit a67aa996 authored by Sameer Ajmani's avatar Sameer Ajmani

go.net/context: split the implementations of Background, WithValue,

WithCancel, and WithTimeout to use different concrete types.  Update the
tests and documentation.

This change reduces the size of context structs, reduces the number of
allocations (see TestAllocs) and removes unnecessary pointers from the
heap, such as the timer field for non-timer contexts.

IMPORTANT: I've removed the code in these functions that handles nil Context parameters.  Passing a nil parent Context will now cause a panic.

LGTM=bcmills
R=bcmills, crawshaw
CC=adonovan, golang-codereviews, rsc
https://golang.org/cl/116400043
parent a1f3609e
This diff is collapsed.
......@@ -13,8 +13,9 @@ import (
"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.
// otherContext is a Context that's not one of the types defined in context.go.
// This lets us test code paths that differ based on the underlying type of the
// Context.
type otherContext struct {
Context
}
......@@ -52,7 +53,7 @@ func TestTODO(t *testing.T) {
func TestWithCancel(t *testing.T) {
c1, cancel := WithCancel(Background())
o := otherContext{c1}
c2 := newCtx(o, maybeCanceled)
c2, _ := WithCancel(o)
contexts := []Context{c1, o, c2}
for i, c := range contexts {
......@@ -86,82 +87,87 @@ func TestWithCancel(t *testing.T) {
}
func TestParentFinishesChild(t *testing.T) {
// Context tree:
// parent -> cancelChild
// parent -> valueChild -> timerChild
parent, cancel := WithCancel(Background())
pctx := parent.(*ctx)
child1 := newCtx(parent, maybeCanceled)
child2 := newCtx(parent, neverCanceled)
cancelChild, stop := WithCancel(parent)
defer stop()
valueChild := WithValue(parent, "key", "value")
timerChild, stop := WithTimeout(valueChild, 10000*time.Hour)
defer stop()
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)
case x := <-cancelChild.Done():
t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x)
case x := <-timerChild.Done():
t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x)
case x := <-valueChild.Done():
t.Errorf("<-valueChild.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)
// The parent's children should contain the two cancelable children.
pc := parent.(*cancelCtx)
cc := cancelChild.(*cancelCtx)
tc := timerChild.(*timerCtx)
pc.mu.Lock()
if len(pc.children) != 2 || !pc.children[cc] || !pc.children[tc] {
t.Errorf("bad linkage: pc.children = %v, want %v and %v",
pc.children, cc, tc)
}
pc.mu.Unlock()
if p, ok := parentCancelCtx(cc.Context); !ok || p != pc {
t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc)
}
if p, ok := parentCancelCtx(tc.Context); !ok || p != pc {
t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc)
}
pctx.mu.Unlock()
cancel()
pctx.mu.Lock()
if len(pctx.children) != 0 {
t.Errorf("pctx.cancel didn't clear pctx.children = %v", pctx.children)
pc.mu.Lock()
if len(pc.children) != 0 {
t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children)
}
pctx.mu.Unlock()
pc.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)
check := func(ctx Context, name string) {
select {
case <-ctx.Done():
default:
t.Errorf("<-%s.Done() blocked, but shouldn't have", name)
}
if e := ctx.Err(); e != Canceled {
t.Errorf("%s.Err() == %v want %v", name, e, Canceled)
}
}
check(parent, "parent")
check(cancelChild, "cancelChild")
check(valueChild, "valueChild")
check(timerChild, "timerChild")
// New should return a canceled context on a canceled parent.
child3 := newCtx(parent, neverCanceled)
// WithCancel should return a canceled context on a canceled parent.
precanceledChild := WithValue(parent, "key", "value")
select {
case <-child3.Done():
case <-precanceledChild.Done():
default:
t.Errorf("<-child3.Done() blocked, but shouldn't have")
t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have")
}
if e := child3.Err(); e != Canceled {
t.Errorf("child3.Err() == %v want %v", e, Canceled)
if e := precanceledChild.Err(); e != Canceled {
t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled)
}
}
func TestChildFinishesFirst(t *testing.T) {
for _, parentMayCancel := range []bool{neverCanceled, maybeCanceled} {
parent := newCtx(nil, parentMayCancel)
cancelable, stop := WithCancel(Background())
defer stop()
for _, parent := range []Context{Background(), cancelable} {
child, cancel := WithCancel(parent)
pctx := parent
cctx := child.(*ctx)
select {
case x := <-parent.Done():
......@@ -171,25 +177,29 @@ func TestChildFinishesFirst(t *testing.T) {
default:
}
if cctx.parent != pctx {
t.Errorf("bad linkage: cctx.parent = %v, parent = %v", cctx.parent, pctx)
cc := child.(*cancelCtx)
pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background()
if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) {
t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok)
}
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)
if pcok {
pc.mu.Lock()
if len(pc.children) != 1 || !pc.children[cc] {
t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc)
}
pctx.mu.Unlock()
pc.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)
if pcok {
pc.mu.Lock()
if len(pc.children) != 0 {
t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children)
}
pc.mu.Unlock()
}
pctx.mu.Unlock()
// child should be finished.
select {
......@@ -225,35 +235,35 @@ func testDeadline(c Context, wait time.Duration, t *testing.T) {
}
func TestDeadline(t *testing.T) {
c, _ := WithDeadline(nil, time.Now().Add(100*time.Millisecond))
c, _ := WithDeadline(Background(), time.Now().Add(100*time.Millisecond))
testDeadline(c, 200*time.Millisecond, t)
c, _ = WithDeadline(nil, time.Now().Add(100*time.Millisecond))
c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond))
o := otherContext{c}
testDeadline(o, 200*time.Millisecond, t)
c, _ = WithDeadline(nil, time.Now().Add(100*time.Millisecond))
c, _ = WithDeadline(Background(), 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)
c, _ := WithTimeout(Background(), 100*time.Millisecond)
testDeadline(c, 200*time.Millisecond, t)
c, _ = WithTimeout(nil, 100*time.Millisecond)
c, _ = WithTimeout(Background(), 100*time.Millisecond)
o := otherContext{c}
testDeadline(o, 200*time.Millisecond, t)
c, _ = WithTimeout(nil, 100*time.Millisecond)
c, _ = WithTimeout(Background(), 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)
c, _ := WithTimeout(Background(), 200*time.Millisecond)
o := otherContext{c}
c, cancel := WithTimeout(o, 400*time.Millisecond)
cancel()
......@@ -291,7 +301,7 @@ func TestValues(t *testing.T) {
c0 := Background()
check(c0, "c0", "", "", "")
c1 := WithValue(nil, k1, "c1k1")
c1 := WithValue(Background(), k1, "c1k1")
check(c1, "c1", "c1k1", "", "")
c2 := WithValue(c1, k2, "c2k2")
......@@ -306,7 +316,7 @@ func TestValues(t *testing.T) {
o0 := otherContext{Background()}
check(o0, "o0", "", "", "")
o1 := otherContext{WithValue(nil, k1, "c1k1")}
o1 := otherContext{WithValue(Background(), k1, "c1k1")}
check(o1, "o1", "c1k1", "", "")
o2 := WithValue(o1, k2, "o2k2")
......@@ -339,7 +349,7 @@ func TestAllocs(t *testing.T) {
c := WithValue(bg, k1, nil)
c.Value(k1)
},
limit: 3,
limit: 1,
gccgoLimit: 3,
},
{
......@@ -348,7 +358,7 @@ func TestAllocs(t *testing.T) {
c, _ := WithTimeout(bg, 15*time.Millisecond)
<-c.Done()
},
limit: 9,
limit: 8,
gccgoLimit: 13,
},
{
......@@ -358,7 +368,7 @@ func TestAllocs(t *testing.T) {
cancel()
<-c.Done()
},
limit: 7,
limit: 5,
gccgoLimit: 8,
},
{
......@@ -368,7 +378,7 @@ func TestAllocs(t *testing.T) {
cancel()
<-c.Done()
},
limit: 16,
limit: 8,
gccgoLimit: 25,
},
} {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment