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.
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