Exploring the context package

This article is the basis for a short talk at the Auckland Go meetup. Go is a programming language devised by some people at Google, and nurtured by an open source community.

I think you should be using the context package in your next Go project.

This is a short talk, so I’ll just cover some key points with running examples.

Note: if you’re reading this as a blog post, you’ll need to be a little familiar with go and the command line.


Background

Package context defines the Context type, which carries deadlines, cancelation signals, and other request-scoped values across API boundaries and between processes.

The context package was created by Brad Fitzpatrick. Before it became public, it was already used internally at Google ‘as the first parameter for all functions’. It first surfaced to the public as an external package, and was then introduced into the standard library in Go1.7, when it was integrated into several standard-libary packages — notably as a member of the http.Request type, and similarly in other packages.

At Hapara we have used context.WithDeadline to protect one of our services from overloading — it provides a basic kind of back-pressure.

The introduction of the Context type, as a member of the Request object, is significant — the standard library now provides features previously supplied by third party packages, such as negroni (and similar), and Gorilla Context.

Example code

Let’s start exploring with some example code, at https://github.com/laher/context-example . Requires Go 1.7, preferably 1.8.

  • Install the examples with `go get github.com/laher/context-example`
  • Run the code with go run *.go
  • Now invoke the service from a separate terminal, with curl -i http://localhost:8765/?d=1s

Handling deadlines (timing out)

Invoke the ‘slow query’ with curl -i http://localhost:8765/?d=4s (note that the service times out after 3 seconds). Backpressure of a sort.

Middleware

Middleware in Go refers to an http handler which wraps around a multiplexer. There are several 3rd party middleware solutions (such as negroni), but really the standard library supports a very similar pattern. The use of a Context in the request allows us to hold data in the request.

* See the example code for invocation and definition.

Cancellation and multiple receivers

  • Run the ‘cancellation’ example with curl -i http://localhost:8765/cancel?d=3s
  • Context.Done() uses a closed channel to ensure that multiple goroutines can receive the same ‘done’ message.

Setting/retrieving values

The Context can store request-scoped variables. It’s useful when writing ‘middleware’, but it’s a little bit ‘anti-pattern’ — it’s a bit magical, because it’s not type-safe. See ‘client ip handling’ for an example of how to encapsulate the casting.

Client disconnection triggers ‘Done’ (since go1.8)

  • Run the code using Go1.8.
  • invoke curl -i http://localhost:8765/?d=3s
  • Cancel (Cmd-C or Ctrl-C) the curl comand prematurely.
  • Observe that the context is cancelled immediately.

Bug-catching - unhandled deadlocks?

  • Run curl -i http://localhost:8765/buggy . See how the server hangs. Nothing is pulling off the channel. Eek!
  • Amend the invocation of WrapContext to use `WrapContextWithHijack instead.
  • Now run curl -i http://localhost:8765/buggy again. See how the service sends a response and closes the connection.
  • Hold on, it’s not being terminated correctly. Fix me.

Thanks, that’s all for now. Hopefully it’s given you some ideas about how you might use the context package.

Further Reading