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