Learning Go: Context package
Tags: go, tutorial
Published: 2021-08-27 02:38:48

Back in August 2016 [Go 1.7 was released](https://blog.golang.org/go1.7), it included among other things a new package called [`context`](https://pkg.go.dev/context). This package was originally implemented in [`golang.org/x/net/context`](https://pkg.go.dev/golang.org/x/net/context) but it was copied over to the standard library during this release.

This change made other standard library packages, like `net`, `net/http` and `database/sql`, to be updated to add support for the `context` package **but** without breaking existing APIs. That's why we see functions with similar name and arguments but with an extra `context` argument added as the first one, like `database/sql.*DB` 's `Ping` method:

```go
func (db *DB) Ping() error
func (db *DB) PingContext(ctx context.Context) error
```

This decision was made because of the [version one compatibility](https://golang.org/doc/go1compat) promise, however behind the scenes most of those methods use `context` but [with default values](https://cs.opensource.google/go/go/+/refs/tags/go1.16.6:src/database/sql/sql.go;l=816), usually `context.Background()`:

```go
func (db *DB) Ping() error {
	return db.PingContext(context.Background())
}
```

One important thing to notice about those new methods is the fact that they defined a _de-facto_ convention where if we need to use `context.Context` in our functions then it should be the first argument called `ctx`.

Let's dive into the `context` package.

## What is in the `context` package?

The `context` package defines a type called `Context` that is used for **deadlines**, **cancellation signals** as well as a way to use **request-scoped values**.

`context.Context` is an interface type that defines four functions:

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

Depending on how we plan to use `context.Context` we may or may not use all the methods:

* `Deadline()`: returns the time when the context should end, if no deadline is set then the returned bool value is `false`. This function is useful in cases where a `context` is received and we want to calculate if there's enough time to complete the work to be done.
* `Done()`: returns a channel that is closed when the context ends, the way this channel is closed depends on how the `context.Context` was initialized, please refer to the docs for [concrete details](https://cs.opensource.google/go/go/+/refs/tags/go1.16.6:src/context/context.go;l=68-77).
* `Err()`: *if* there was an error then it returns a non-nil error when the returned channel in `Done()` was closed, either `context.DeadlineExceeded` or `context.Canceled`, otherwise `nil`.
* `Value(key interface{})`: it's used to get a request-scoped value stored in the _context_, used in conjunction with [`context.WithValue`](https://pkg.go.dev/context#WithValue).

It's most likely you're already using `context` in one way or another, for example if you're using `database/sql` or `net/http`; some projects rely heavily on `context` to achieve their goal, for example [OpenTelemetry](https://mariocarrion.com/2021/05/13/golang-microservices-opentelemetry.html) uses it intensively for instrumentation.

> The code used for this post is [available on Github](https://github.com/MarioCarrion/videos/tree/563b41660420a5e77d25157f1d4798f343d12d22/2021/05/07).

### Deadlines

**Deadlines** define a way to indicate something has been completed using time, there are two ways to do that:

![Deadlines](https://mariocarrion.com/img/2021/05/31/deadlines.png?src=devto)

* [`context.WithDeadline`](https://pkg.go.dev/context#WithDeadline): uses a concrete `time.Time` to indicate when the _context_ should finish, and
* [`context.WithTimeout`](https://pkg.go.dev/context#WithTimeout): uses a relative `time.Duration` to indicate when the _context_ should finish.

If you refer to the [source code](https://cs.opensource.google/go/go/+/refs/tags/go1.16.6:src/context/context.go;l=502) you will notice that `context.WithTimeout` uses `context.WithDeadline` behind the scenes but adding the timeout to the current time to indicate the deadline:

```go
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}
```

Both functions return a new `context.Context` as well as a `context.CancelFunc` function; this new `context.Context` is a copy of the parent one with deadline details attached to it and it's meant to be used as the argument for any subsequent calls that are supposed to be using a deadline.

The function `context.CancelFunc` should be called (usually via a `defer`) when the corresponding block of code using the new `context.Context` is completed, this is to propagate the cancellation to other functions using the `context` in case the deadline was reached.

For [example](https://github.com/MarioCarrion/videos/blob/563b41660420a5e77d25157f1d4798f343d12d22/2021/05/07/main_deadline.go):

```go
ctx, cancel := context.WithTimeout(context.Background(), 1 * time.Millisecond)
defer cancel()

select {
case <-time.After(1 * time.Second):
	fmt.Println("overslept")
case <-ctx.Done():
	fmt.Println(ctx.Err()) // prints "context deadline exceeded"
}
```

The code above will always print `context deadline exceeded` because the value we indicated in the call `context.WithTimeout` is _1 millisecond_, if we modify that value to be something higher than _1 second_ then it will print out `overslept`.

This is because the `select` is expecting for one of two channels to receive a message, either the one returned by `time` (via `time.After()`) or the one indicated in `context` (via `ctx.Done()`).

### Cancellation signals

**Cancellation signals** define a way to indicate something has been completed by explicitly calling a `CancelFunc` function, there is one way to do it:

![Cancellation](https://mariocarrion.com/img/2021/05/31/cancellation.png)

* [`context.WithCancel`](https://pkg.go.dev/context#WithCancel): it returns a copy of the _context_ and a `CancelFunc` to indicate when to cancel some work.

Similar to the _Deadlines_, for **Cancellation signals** a _context.Context_ is returned as well as a `CancelFunc`; this function should be explicitly called to indicate when a returned _context_ is canceled to propagate to other functions using the same _context_ the work should stop.

The difference between `context.WithCancel` and `context.WithDeadline`/`context.WithTimeout` is the explicitness, so instead of defining a _timeout_ the `CancelFunc` should be called explicitly. In all three cases we always need to call the returned `CancelFunc` to properly propagate the cancellation details to other functions using the same context.

The function `context.CancelFunc` should be called (usually via a `defer`) when the corresponding block of code using the new `context.Context` is completed, this is to propagate the cancellation to other functions using the `context` in case the deadline was reached.

For [example](https://github.com/MarioCarrion/videos/blob/563b41660420a5e77d25157f1d4798f343d12d22/2021/05/07/main_cancel.go):

```go
ch := make(chan struct{})

run := func(ctx context.Context) {
	n := 1
	for {
		select {
		case <-ctx.Done(): // 2. "ctx" is cancelled, we close "ch"
			fmt.Println("exiting")
			close(ch)
			return // returning not to leak the goroutine
		default:
			time.Sleep(time.Millisecond * 300)
			fmt.Println(n)
			n++
		}
	}
}

ctx, cancel := context.WithCancel(context.Background())
go func() {
	time.Sleep(time.Second * 2)
	fmt.Println("goodbye")
	cancel() // 1. cancels "ctx"
}()

go run(ctx)

fmt.Println("waiting to cancel...")

<-ch // 3. "ch" is closed, we exit

fmt.Println("bye")
```

The code above is a bit more elaborated than the one used for _Deadlines_, the key part is the explicit `cancel()` call in the goroutine; that `cancel()` call is what in the end stops the `run` function that receives the _cancelable context_.

### Request-scoped values

**Request-scoped values** define a way to set and get values that apply to concrete instances of _context.Context_, they are meant to be used **only** during a user request, for example during an HTTP request to pass down information to the subsequent internal calls, there is one way to do it:

![Request-scoped values](https://mariocarrion.com/img/2021/05/31/request-scoped.png)

* [`context.WithValue`](https://pkg.go.dev/context#WithValue): it returns a copy of the _context_ that happens to include the value set.

For [example](https://github.com/MarioCarrion/videos/blob/563b41660420a5e77d25157f1d4798f343d12d22/2021/05/07/main_value.go):

```go
ctx := context.WithValue(context.Background(), auth, "Bearer hi")

//-

bearer := ctx.Value(auth)
str, ok := bearer.(string)
if !ok {
	log.Fatalln("not a string")
}

fmt.Println("value:", str)
```

The code above uses both `context.WithValue` and `context.Context.Value`; `context.WithValue` returns a copy of the parent _context_ with the associated value and key, then we can use that returned _context_ and call its method `Value` to get the previously assigned value.

The complexity of `context.WithValue` is not about the implementation or usage but rather _when_ to make those calls, recall the point of using this function is **only** for request-scoped values not things meant to live all the time during the execution of the program.

Some common examples include defining a value for JSON Web Tokens or extra headers, in both cases the values are meant to be passed around multiple requests to augment the subsequent requests.

## Conclusion

Understanding how to use `context.Context` is important when dealing with instructions that require cancellation, for example HTTP requests, database commands or remote produce calls; not only because we need to define a sane value to indicate when to stop a running request to avoid waiting forever but also to identify when a remote call was canceled to properly react to that event.

`context.Context` provides a simpler way to deal with multiple goroutines to coordinate their work, to identify timeouts and to determine when those happen. Because instrumentation is an important part of any distributed system, knowing how those values are sent and defined between different calls is useful to understand the flow of our program.

## Recommended Reading

If you're looking to expand more about `context.Context`, I recommend reading the following links:

* [Complex Pipelines in Go (Part 4): Sane Coordination and Cancellation](https://mariocarrion.com/2020/08/19/go-implementing-complex-pipelines-part-4.html)
* [The Go Blog: Contexts and structs](https://blog.golang.org/context-and-structs)
* [The Go Blog: Go Concurrency Patterns: Context](https://blog.golang.org/context)