blob: 42bcfc4c2686b92435d09e50369c4c08c72ab5eb [file] [log] [blame]
Go Concurrency Patterns: Context
29 Jul 2014
Tags: concurrency, cancelation, cancellation, context
Sameer Ajmani
* Introduction
In Go servers, each incoming request is handled in its own goroutine.
Request handlers often start additional goroutines to access backends such as
databases and RPC services.
The set of goroutines working on a request typically needs access to
request-specific values such as the identity of the end user, authorization
tokens, and the request's deadline.
When a request is canceled or times out, all the goroutines working on that
request should exit quickly so the system can reclaim any resources they are
using.
At Google, we developed a `context` package that makes it easy to pass
request-scoped values, cancelation signals, and deadlines across API boundaries
to all the goroutines involved in handling a request.
The package is publicly available as
[[https://golang.org/pkg/context][context]].
This article describes how to use the package and provides a complete working
example.
* Context
The core of the `context` package is the `Context` type:
.code context/interface.go /A Context/,/^}/
(This description is condensed; the
[[https://golang.org/pkg/context][godoc]] is authoritative.)
The `Done` method returns a channel that acts as a cancelation signal to
functions running on behalf of the `Context`: when the channel is closed, the
functions should abandon their work and return.
The `Err` method returns an error indicating why the `Context` was canceled.
The [[/pipelines][Pipelines and Cancelation]] article discusses the `Done`
channel idiom in more detail.
A `Context` does _not_ have a `Cancel` method for the same reason the `Done`
channel is receive-only: the function receiving a cancelation signal is usually
not the one that sends the signal.
In particular, when a parent operation starts goroutines for sub-operations,
those sub-operations should not be able to cancel the parent.
Instead, the `WithCancel` function (described below) provides a way to cancel a
new `Context` value.
A `Context` is safe for simultaneous use by multiple goroutines.
Code can pass a single `Context` to any number of goroutines and cancel that
`Context` to signal all of them.
The `Deadline` method allows functions to determine whether they should start
work at all; if too little time is left, it may not be worthwhile.
Code may also use a deadline to set timeouts for I/O operations.
`Value` allows a `Context` to carry request-scoped data.
That data must be safe for simultaneous use by multiple goroutines.
** Derived contexts
The `context` package provides functions to _derive_ new `Context` values from
existing ones.
These values form a tree: when a `Context` is canceled, all `Contexts` derived
from it are also canceled.
`Background` is the root of any `Context` tree; it is never canceled:
.code context/interface.go /Background returns/,/func Background/
`WithCancel` and `WithTimeout` return derived `Context` values that can be
canceled sooner than the parent `Context`.
The `Context` associated with an incoming request is typically canceled when the
request handler returns.
`WithCancel` is also useful for canceling redundant requests when using multiple
replicas.
`WithTimeout` is useful for setting a deadline on requests to backend servers:
.code context/interface.go /WithCancel/,/func WithTimeout/
`WithValue` provides a way to associate request-scoped values with a `Context`:
.code context/interface.go /WithValue/,/func WithValue/
The best way to see how to use the `context` package is through a worked
example.
* Example: Google Web Search
Our example is an HTTP server that handles URLs like
`/search?q=golang&timeout=1s` by forwarding the query "golang" to the
[[https://developers.google.com/web-search/docs/][Google Web Search API]] and
rendering the results.
The `timeout` parameter tells the server to cancel the request after that
duration elapses.
The code is split across three packages:
- [[context/server/server.go][server]] provides the `main` function and the handler for `/search`.
- [[context/userip/userip.go][userip]] provides functions for extracting a user IP address from a request and associating it with a `Context`.
- [[context/google/google.go][google]] provides the `Search` function for sending a query to Google.
** The server program
The [[context/server/server.go][server]] program handles requests like
`/search?q=golang` by serving the first few Google search results for `golang`.
It registers `handleSearch` to handle the `/search` endpoint.
The handler creates an initial `Context` called `ctx` and arranges for it to be
canceled when the handler returns.
If the request includes the `timeout` URL parameter, the `Context` is canceled
automatically when the timeout elapses:
.code context/server/server.go /func handleSearch/,/defer cancel/
The handler extracts the query from the request and extracts the client's IP
address by calling on the `userip` package.
The client's IP address is needed for backend requests, so `handleSearch`
attaches it to `ctx`:
.code context/server/server.go /Check the search query/,/userip.NewContext/
The handler calls `google.Search` with `ctx` and the `query`:
.code context/server/server.go /Run the Google search/,/elapsed/
If the search succeeds, the handler renders the results:
.code context/server/server.go /resultsTemplate/,/}$/
** Package userip
The [[context/userip/userip.go][userip]] package provides functions for
extracting a user IP address from a request and associating it with a `Context`.
A `Context` provides a key-value mapping, where the keys and values are both of
type `interface{}`.
Key types must support equality, and values must be safe for simultaneous use by
multiple goroutines.
Packages like `userip` hide the details of this mapping and provide
strongly-typed access to a specific `Context` value.
To avoid key collisions, `userip` defines an unexported type `key` and uses
a value of this type as the context key:
.code context/userip/userip.go /The key type/,/const userIPKey/
`FromRequest` extracts a `userIP` value from an `http.Request`:
.code context/userip/userip.go /func FromRequest/,/}/
`NewContext` returns a new `Context` that carries a provided `userIP` value:
.code context/userip/userip.go /func NewContext/,/}/
`FromContext` extracts a `userIP` from a `Context`:
.code context/userip/userip.go /func FromContext/,/}/
** Package google
The [[context/google/google.go][google.Search]] function makes an HTTP request
to the [[https://developers.google.com/web-search/docs/][Google Web Search API]]
and parses the JSON-encoded result.
It accepts a `Context` parameter `ctx` and returns immediately if `ctx.Done` is
closed while the request is in flight.
The Google Web Search API request includes the search query and the user IP as
query parameters:
.code context/google/google.go /func Search/,/q.Encode/
`Search` uses a helper function, `httpDo`, to issue the HTTP request and cancel
it if `ctx.Done` is closed while the request or response is being processed.
`Search` passes a closure to `httpDo` handle the HTTP response:
.code context/google/google.go /var results/,/return results/
The `httpDo` function runs the HTTP request and processes its response in a new
goroutine.
It cancels the request if `ctx.Done` is closed before the goroutine exits:
.code context/google/google.go /func httpDo/,/^}/
* Adapting code for Contexts
Many server frameworks provide packages and types for carrying request-scoped
values.
We can define new implementations of the `Context` interface to bridge between
code using existing frameworks and code that expects a `Context` parameter.
For example, Gorilla's
[[http://www.gorillatoolkit.org/pkg/context][github.com/gorilla/context]]
package allows handlers to associate data with incoming requests by providing a
mapping from HTTP requests to key-value pairs.
In [[context/gorilla/gorilla.go][gorilla.go]], we provide a `Context`
implementation whose `Value` method returns the values associated with a
specific HTTP request in the Gorilla package.
Other packages have provided cancelation support similar to `Context`.
For example, [[http://godoc.org/gopkg.in/tomb.v2][Tomb]] provides a `Kill`
method that signals cancelation by closing a `Dying` channel.
`Tomb` also provides methods to wait for those goroutines to exit, similar to
`sync.WaitGroup`.
In [[context/tomb/tomb.go][tomb.go]], we provide a `Context` implementation that
is canceled when either its parent `Context` is canceled or a provided `Tomb` is
killed.
* Conclusion
At Google, we require that Go programmers pass a `Context` parameter as the
first argument to every function on the call path between incoming and outgoing
requests.
This allows Go code developed by many different teams to interoperate well.
It provides simple control over timeouts and cancelation and ensures that
critical values like security credentials transit Go programs properly.
Server frameworks that want to build on `Context` should provide implementations
of `Context` to bridge between their packages and those that expect a `Context`
parameter.
Their client libraries would then accept a `Context` from the calling code.
By establishing a common interface for request-scoped data and cancelation,
`Context` makes it easier for package developers to share code for creating
scalable services.