Introduction

The Go programming language has the built-in keyword go to create goroutines, but has no keywords or direct support for terminating goroutines. In a real world service, the ability to time-out and terminate goroutines is critical for maintaining the health and operation of a service. No request or task can be allowed to run forever so identifying and managing latency is a responsibility every programmer has.

A solution provided by the Go team to solve this problem is the Context package. It was written and introduced by Sameer Ajmani back in 2014 at the Gotham Go conference. He also wrote a blog post for the Go blog.

Talk Video: https://vimeo.com/115309491

Slide Deck: https://talks.golang.org/2014/gotham-context.slide#1

Blog Post: https://blog.golang.org/context

Through this published work and conversations I’ve had with Sameer over the years, a set of semantics have evolved. In this post, I will provide these semantics and do my best to show you examples in code.

Incoming requests to a server should create a Context

The time to create a Context is always as early as possible in the processing of a request or task. Working with Context early in the development cycle will force you to design API’s to take a Context as the first parameter. Even if you are not 100% sure a function needs a Context, it’s easier to remove the Context from a few functions than try to add Context later.

Listing 1

https://github.com/ardanlabs/service/blob/master/internal/platform/web/web.go#L75

 75 // Handle is our mechanism for mounting Handlers for a given HTTP verb and path
 76 // pair, this makes for really easy, convenient routing.
 77 func (a *App) Handle(verb, path string, handler Handler, mw ...Middleware) {
...
 85     // The function to execute for each request.
 86     h := func(w http.ResponseWriter, r *http.Request, params map[string]string) {
 87         ctx, span := trace.StartSpan(r.Context(), "internal.platform.web")
 88         defer span.End()
...
106    // Add this handler for the specified verb and route.
107    a.TreeMux.Handle(verb, path, h)
108 }

In listing 1, you see code taken from the service project we teach at Ardan Labs. Line 86 defines a handler function that is bound to all routes as shown on line 107. It’s this function that starts to process any incoming requests. On line 87, a span is created for the request which takes as its first parameter a Context. This is the first time in the service code a Context is needed.

What’s great here is that the http.Request value already contains a Context. This was added in version 1.7 of Go. This means the code doesn’t need to manually create a top-level Context. If we were using version 1.8 of Go, then you would need to create an empty Context before the call to StartSpan by using the context.Background function.

Listing 2

https://golang.org/pkg/context/#Background

87         ctx := context.Background()
88         ctx, span := trace.StartSpan(ctx, "internal.platform.web")
89         defer span.End()

Listing 2 shows what the code would have to look like in version 1.8 of Go. As it’s described in the package documentation,

Background returns a non-nil, empty Context. It’s never canceled, has no values, and has no deadline. It is typically used by the main function, initialization, and tests, and as the top-level Context for incoming requests.

It’s an idiom in Go to use the variable name ctx for all Context values. Since a Context is an interface, no pointer semantics should be used.

Listing 3

https://golang.org/pkg/context/#Context

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

Every function that accepts a Context should get its own copy of the interface value.

#go

Context Package Semantics In Go
1.10 GEEK