Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Some context on Context

Some context on Context

With Go 1.7 the context package was added to Go's standard library, but many still are figuring out what this is actually for and what are the best practices when it comes to adding it to their APIs. This talk will cover the topic in detail, not only explaining how it is to be used. It will also get into the internal details and show some of the most beautiful pieces of code in the context package.

Francesc Campoy Flores

February 24, 2017
Tweet

More Decks by Francesc Campoy Flores

Other Decks in Programming

Transcript

  1. Is the WiFi working? func pingGoogle(ctx context.Context) error { res,

    err := http.Get(“https://google.com”) if err != nil { return err } if res.StatusCode != http.StatusOK { return fmt.Errorf(“got %s from Google”, res.Status) } return nil }
  2. func main() { if err := pingGoogle(); err != nil

    { log.Printf(“could not ping Google: %v”, err) } } Is the WiFi working?
  3. the context package - Added to the standard library with

    go 1.7 in 2016 - golang.org/x/net/context in 2014 - created at Google in 2013 - 492 L.O.C. (369 without comments) - Exposes only two types, one of them just for documentation. How interesting could it be?
  4. type Context interface { Done() <-chan struct{} Err() error Deadline()

    (deadline time.Time, ok bool) Value(key interface{}) interface{} } var Canceled, DeadlineExceeded error the Context interface
  5. type Context interface { Done() <-chan struct{} Err() error Deadline()

    (deadline time.Time, ok bool) Value(key interface{}) interface{} } var Canceled, DeadlineExceeded error the Context interface
  6. type Context interface { Done() <-chan struct{} Err() error Deadline()

    (deadline time.Time, ok bool) Value(key interface{}) interface{} } var Canceled, DeadlineExceeded error the Context interface
  7. func pingWithContext(ctx context.Context) error { errc := make(chan error) go

    func() { errc <- pingGoogle() }() Using the Context interface
  8. func pingWithContext(ctx context.Context) error { errc := make(chan error) go

    func() { errc <- pingGoogle() }() select { case err := <-errc: return err Using the Context interface
  9. func pingWithContext(ctx context.Context) error { errc := make(chan error) go

    func() { errc <- pingGoogle() }() select { case err := <-errc: return err case <-ctx.Done(): return ctx.Err() } } Using the Context interface
  10. func main() { ctx := ??? if err := pingWithContext(ctx);

    err != nil { log.Printf(“could not ping Google: %v”, err) } } Using the Context interface
  11. func Background() Context func TODO() Context func WithCancel(parent Context) (ctx

    Context, cancel CancelFunc) func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) func WithValue(parent Context, key, val interface{}) Context creating Context values
  12. func Background() Context func TODO() Context func WithCancel(parent Context) (ctx

    Context, cancel CancelFunc) func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) func WithValue(parent Context, key, val interface{}) Context creating Context values
  13. func main() { ctx := context.Background() if err := pingWithContext(ctx);

    err != nil { log.Printf(“could not ping Google: %v”, err) } } Using the Context interface
  14. func main() { ctx := context.TODO() if err := pingWithContext(ctx);

    err != nil { log.Printf(“could not ping Google: %v”, err) } } Using the Context interface
  15. func Background() Context func TODO() Context func WithCancel(parent Context) (ctx

    Context, cancel CancelFunc) func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) func WithValue(parent Context, key, val interface{}) Context creating Context values
  16. func main() { ctx := context.Background() ctx, cancel := context.WithCancel(ctx)

    time.AfterFunc(1 * time.Second, cancel) if err := pingWithContext(ctx); err != nil { log.Printf(“could not ping Google: %v”, err) } } Using the Context interface
  17. func Background() Context func TODO() Context func WithCancel(parent Context) (ctx

    Context, cancel CancelFunc) func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) func WithValue(parent Context, key, val interface{}) Context creating Context values
  18. func main() { ctx := context.Background() ctx, cancel := context.WithTimeout(ctx,

    time.Second) if err := pingWithContext(ctx); err != nil { log.Printf(“could not ping Google: %v”, err) } } Using the Context interface
  19. func Background() Context func TODO() Context func WithCancel(parent Context) (ctx

    Context, cancel CancelFunc) func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) func WithValue(parent Context, key, val interface{}) Context creating Context values
  20. type key int const userIDKey = 0 func main() {

    ctx := context.WithValue(userIDKey, 12345) if err := pingWithContext(ctx); err != nil { log.Printf(“could not ping Google: %v”, err) } } Using the Context interface
  21. var Canceled = errors.New("context canceled") var DeadlineExceeded error = deadlineExceededError{}

    type deadlineExceededError struct{} func (deadlineExceededError) Error() string { return "..." } func (deadlineExceededError) Timeout() bool { return true } func (deadlineExceededError) Temporary() bool { return true } errors in context
  22. var Canceled = errors.New("context canceled") var DeadlineExceeded error = deadlineExceededError{}

    type deadlineExceededError struct{} func (deadlineExceededError) Error() string { return "..." } func (deadlineExceededError) Timeout() bool { return true } func (deadlineExceededError) Temporary() bool { return true } errors in context
  23. var Canceled = errors.New("context canceled") var DeadlineExceeded error = deadlineExceededError{}

    type deadlineExceededError struct{} func (deadlineExceededError) Error() string { return "..." } func (deadlineExceededError) Timeout() bool { return true } func (deadlineExceededError) Temporary() bool { return true } errors in context
  24. if tmp, ok := err.(interface { Temporary() bool }); ok

    { if tmp.Temporary() { // retry } else { // report } } errors in context
  25. type Context interface { Done() <-chan struct{} Err() error Deadline()

    (deadline time.Time, ok bool) Value(key interface{}) interface{} } var Canceled, DeadlineExceeded error the Context interface
  26. type Context interface { Done() <-chan struct{} Err() error Deadline()

    (deadline time.Time, ok bool) Value(key interface{}) interface{} } var Canceled, DeadlineExceeded error the Context interface
  27. type Context interface { Done() <-chan struct{} Err() error Deadline()

    (deadline time.Time, ok bool) Value(key interface{}) interface{} } var Canceled, DeadlineExceeded error the Context interface
  28. Interface values are comparable. Two interface values are equal if

    they have identical dynamic types and equal dynamic values or if both have value nil. - Go specification
  29. Interface values are comparable. Two interface values are equal if

    they have identical dynamic types and equal dynamic values or if both have value nil. - Go specification
  30. type myLibKey int ctx = WithValue(ctx, myLibKey(42), “one thing”) type

    myOtherLibKey int ctx = WithValue(ctx, myOtherLibKey(42), “the other thing”) context key types
  31. context key types type myLibKey int ctx = WithValue(ctx, myLibKey(42),

    “one thing”) type myOtherLibKey int ctx = WithValue(ctx, myOtherLibKey(42), “the other thing”) ctx.Value(myLibKey(42)) ctx.Value(myOtherLibKey(42))
  32. type Context interface { Done() <-chan struct{} Err() error Deadline()

    (deadline time.Time, ok bool) Value(key interface{}) interface{} } var Canceled, DeadlineExceeded error the Context interface
  33. type Context interface { Done() <-chan struct{} Err() error Deadline()

    (deadline time.Time, ok bool) Value(key interface{}) interface{} Cancel() } the Context interface
  34. func doAll(ctx context.Context) { ctx, cancel := context.WithCancel(ctx) defer cancel()

    for i := 0; i < 10; i++ { go doOne(ctx) } … } the Context interface
  35. type Context interface { Done() <-chan struct{} Err() error Deadline()

    (deadline time.Time, ok bool) Value(key interface{}) interface{} } the Context interface
  36. type context struct { done <-chan struct{} err error deadline

    time.Time Values map[interface{}]interface{} } the Context struct?
  37. the empty context type emptyCtx struct{} var background = new(emptyCtx)

    var todo = new(emptyCtx) func (e *emptyCtx) String() string { switch e { case background: return "context.Background" case todo: return "context.TODO" } return "unknown empty Context" }
  38. the empty context type emptyCtx struct{} var background = new(emptyCtx)

    var todo = new(emptyCtx) func (e *emptyCtx) String() string { switch e { case background: return "context.Background" case todo: return "context.TODO" } return "unknown empty Context" }
  39. fmt.Println(todo) // background the empty context type emptyCtx struct{} var

    background = new(emptyCtx) var todo = new(emptyCtx) func (e *emptyCtx) String() string { switch e { case background: return "context.Background" case todo: return "context.TODO" } return "unknown empty Context" }
  40. the empty context type emptyCtx struct{} var background = new(emptyCtx)

    // 0x1040a124 var todo = new(emptyCtx) // 0x1040a124 func (e *emptyCtx) String() string { switch e { case background: return "context.Background" case todo: return "context.TODO" } return "unknown empty Context" }
  41. the “empty” context type emptyCtx int var background = new(emptyCtx)

    var todo = new(emptyCtx) func (e *emptyCtx) String() string { switch e { case background: return "context.Background" case todo: return "context.TODO" } return "unknown empty Context" }
  42. func (c cancelCtx) Done() <-chan struct{} { return c.done }

    func (c cancelCtx) Err() error { c.mu.Lock() defer c.mu.Unlock() return c.err } the cancel context
  43. func (c cancelCtx) Done() <-chan struct{} { return c.done }

    func (c cancelCtx) Err() error { c.mu.Lock() defer c.mu.Unlock() return c.err } the cancel context
  44. type cancelCtx struct { Context // provides and Value, Deadline

    done chan struct{} mu sync.Mutex err error } the cancel context
  45. func WithCancel(parent Context) (Context, CancelFunc) { child := newCancelCtx(parent) go

    func() { select { case <-parent.Done(): child.cancel(false, parent.Err()) case <-child.Done(): } }() … } cancellation cascading
  46. func WithCancel(parent Context) (Context, CancelFunc) { child := newCancelCtx(parent) go

    func() { select { case <-parent.Done(): child.cancel(false, parent.Err()) case <-child.Done(): } }() … } cancellation cascading
  47. func WithCancel(parent Context) (Context, CancelFunc) { child := newCancelCtx(parent) go

    func() { select { case <-parent.Done(): child.cancel(false, parent.Err()) case <-child.Done(): } }() … } cancellation cascading
  48. cancellation cascading // A canceler is a context type that

    can be canceled directly. The // implementations are *cancelCtx and *timerCtx. type canceler interface { cancel(removeFromParent bool, err error) Done() <-chan struct{} }