Commit bf13cf4e authored by Sameer Ajmani's avatar Sameer Ajmani

go.net/context: remove the Key type; replace it with interface{}. This

makes the Context interface dependent only on standard packages, which
means types in other packages can implement this interface without
depending on go.net/context.

Remove the NewKey function and add examples showing how to use
unexported types to avoid key collisions.  This is the same model used
by http://www.gorillatoolkit.org/pkg/context, except we associate values
with a specific Context instead of storing them in a package-level map.

LGTM=crawshaw
R=golang-codereviews, crawshaw, dsymonds
CC=golang-codereviews, rsc
https://golang.org/cl/104480044
parent 6b9623cc
...@@ -11,7 +11,13 @@ ...@@ -11,7 +11,13 @@
// Context, optionally replacing it with a modified copy created using // Context, optionally replacing it with a modified copy created using
// WithDeadline, WithTimeout, WithCancel, or WithValue. // WithDeadline, WithTimeout, WithCancel, or WithValue.
// //
// Functions that require a Context should take it as the first parameter, named ctx: // Programs that use Contexts should follow these rules to keep interfaces
// consistent across packages and enable static analysis tools to check context
// propagation:
//
// Do not store Contexts inside a struct type; instead, pass a Context
// explicitly to each function that needs it. The Context should be the first
// parameter, typically named ctx:
// //
// func DoSomething(ctx context.Context, arg Arg) error { // func DoSomething(ctx context.Context, arg Arg) error {
// // ... use ctx ... // // ... use ctx ...
...@@ -20,73 +26,19 @@ ...@@ -20,73 +26,19 @@
// Do not pass a nil Context, even if a function permits it. Pass context.TODO // Do not pass a nil Context, even if a function permits it. Pass context.TODO
// if you are unsure about which Context to use. // if you are unsure about which Context to use.
// //
// Future packages will create Contexts from standard request types like // Use context Values only for request-scoped data that transits processes and
// *http.Request. // APIs, not for passing optional parameters to functions.
//
// The same Context may be passed to functions running in different goroutines;
// Contexts are safe for simultaneous use by multiple goroutines.
package context package context
import ( import (
"errors" "errors"
"fmt"
"sync" "sync"
"time" "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 // A Context carries deadlines, and cancellation signals, and other values
// across API boundaries. // across API boundaries.
// //
...@@ -125,12 +77,54 @@ type Context interface { ...@@ -125,12 +77,54 @@ type Context interface {
// Err returns a non-nil error value after Done is closed. Err returns // Err returns a non-nil error value after Done is closed. Err returns
// Canceled if the context was canceled; Err returns DeadlineExceeded if // Canceled if the context was canceled; Err returns DeadlineExceeded if
// the context's deadline passed. No other values for Err are defined. // the context's deadline passed. No other values for Err are defined.
// After Done is closed, successive calls to Err return the same value.
Err() error Err() error
// Value returns the value associated with this context for key, or nil // Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with // if no value is associated with key. Successive calls to Value with
// the same key returns the same result. // the same key returns the same result.
Value(key Key) interface{} //
// Use context values only for request-scoped data that transits
// processes and APIs, not for passing optional parameters to functions.
//
// A 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. A key can be any type that supports equality;
// packages should define keys as an unexported type to avoid
// collisions.
//
// Packages that define a Context key should provide type-safe accessors
// for the values stores using that key:
//
// // Package user defines a User type that's stored in Contexts.
// package user
//
// import "code.google.com/p/go.net/context"
//
// // User is the type of value stored in the Contexts.
// type User struct {...}
//
// // key is an unexported type for keys defined in this package.
// // This prevents collisions with keys defined in other packages.
// type key int
//
// // userKey is the key for user.User values in Contexts. It is
// // unexported; clients use user.NewContext and user.FromContext
// // instead of using this key directly.
// var userKey key = 0
//
// // NewContext returns a new Context that carries value u.
// func NewContext(ctx context.Context, u *User) context.Context {
// return context.WithValue(userKey, u)
// }
//
// // FromContext returns the User value stored in ctx, if any.
// func FromContext(ctx context.Context) (*User, bool) {
// u, ok := ctx.Value(userKey).(*User)
// return u, ok
// }
Value(key interface{}) interface{}
} }
// Canceled is the error returned by Context.Err when the context is canceled. // Canceled is the error returned by Context.Err when the context is canceled.
...@@ -147,7 +141,7 @@ type ctx struct { ...@@ -147,7 +141,7 @@ type ctx struct {
parent Context // set by newCtx parent Context // set by newCtx
done chan struct{} // closed by the first cancel call. nil if uncancelable. done chan struct{} // closed by the first cancel call. nil if uncancelable.
key Key // set by WithValue key interface{} // set by WithValue
val interface{} // set by WithValue val interface{} // set by WithValue
deadline time.Time // set by WithDeadline deadline time.Time // set by WithDeadline
...@@ -174,7 +168,7 @@ func (c *ctx) Err() error { ...@@ -174,7 +168,7 @@ func (c *ctx) Err() error {
return c.err return c.err
} }
func (c *ctx) Value(key Key) interface{} { func (c *ctx) Value(key interface{}) interface{} {
if c.key == key { if c.key == key {
return c.val return c.val
} }
...@@ -269,13 +263,14 @@ func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { ...@@ -269,13 +263,14 @@ func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout)) return WithDeadline(parent, time.Now().Add(timeout))
} }
// WithValue returns a copy of parent in which the value associated with k is v. // WithValue returns a copy of parent in which the value associated with key is
// val.
// //
// Use context Values only for request-scoped data that transits processes and // Use context Values only for request-scoped data that transits processes and
// APIs, not for passing optional parameters to functions. // APIs, not for passing optional parameters to functions.
func WithValue(parent Context, k Key, v interface{}) Context { func WithValue(parent Context, key interface{}, val interface{}) Context {
c := newCtx(parent, neverCanceled) c := newCtx(parent, neverCanceled)
c.key, c.val = k, v c.key, c.val = key, val
return c return c
} }
......
...@@ -249,41 +249,55 @@ func TestCancelledTimeout(t *testing.T) { ...@@ -249,41 +249,55 @@ func TestCancelledTimeout(t *testing.T) {
} }
} }
var k1, k2 = NewKey("k1"), NewKey("k2") type key1 int
type key2 int
var k1 = key1(1)
var k2 = key2(1) // same int as k1, different type
var k3 = key2(3) // same type as k2, different int
func TestValues(t *testing.T) { func TestValues(t *testing.T) {
check := func(c Context, nm, v1, v2 string) { check := func(c Context, nm, v1, v2, v3 string) {
if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 { 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) 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 { 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) t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0)
} }
if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 {
t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0)
}
} }
c0 := Background() c0 := Background()
check(c0, "c0", "", "") check(c0, "c0", "", "", "")
c1 := WithValue(nil, k1, "c1k1") c1 := WithValue(nil, k1, "c1k1")
check(c1, "c1", "c1k1", "") check(c1, "c1", "c1k1", "", "")
c2 := WithValue(c1, k2, "c2k2") c2 := WithValue(c1, k2, "c2k2")
check(c2, "c2", "c1k1", "c2k2") check(c2, "c2", "c1k1", "c2k2", "")
c3 := WithValue(c2, k1, nil) c3 := WithValue(c2, k3, "c3k3")
check(c3, "c3", "", "c2k2") check(c3, "c2", "c1k1", "c2k2", "c3k3")
c4 := WithValue(c3, k1, nil)
check(c4, "c4", "", "c2k2", "c3k3")
o0 := otherContext{Background()} o0 := otherContext{Background()}
check(o0, "o0", "", "") check(o0, "o0", "", "", "")
o1 := otherContext{WithValue(nil, k1, "c1k1")} o1 := otherContext{WithValue(nil, k1, "c1k1")}
check(o1, "o1", "c1k1", "") check(o1, "o1", "c1k1", "", "")
o2 := WithValue(o1, k2, "o2k2") o2 := WithValue(o1, k2, "o2k2")
check(o2, "o2", "c1k1", "o2k2") check(o2, "o2", "c1k1", "o2k2", "")
o3 := otherContext{c4}
check(o3, "o3", "", "c2k2", "c3k3")
o3 := otherContext{c3} o4 := WithValue(o3, k3, nil)
check(o3, "o3", "", "c2k2") check(o4, "o4", "", "c2k2", "")
} }
func TestAllocs(t *testing.T) { func TestAllocs(t *testing.T) {
......
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