January 9, 2022

Context in Go

Go is a very pragmatic language. If there is a thing in the Go standard library, there is an actual practical reason for it. The same stays valid for Context.

for impatient

use to specify a deadline, timeout, or cancel the running goroutine

Use Context when you need:

use for sharing request-scoped values

Use Context if you need to share request-scoped values across API boundaries.

A trace id can be used for tracing operations between microservices. You can receive it in the HTTP request as a header and then pass it to other functions using context, and if the functions perform calls to other microservices, they can extract the trace id and pass it further.

for curious

channels and goroutines

A quick reminder that Go is famous for implementing CSP with channels and goroutines. You write functions, run them concurrently with the go command, and communicate with channels. It is that simple:

package main

import (
    "fmt"
    "time"
)

func main() {
    numbers := make(chan int)
    results := make(chan int)

    ticker := time.NewTicker(1 * time.Second)
    timer := time.NewTimer(5 * time.Second)

    go compute(numbers, results)

    n := 0
    for {
        select {
        case <-ticker.C:
            fmt.Printf("number: %d\n", n)
            numbers <- n
            n++
        case r := <-results:
            fmt.Printf("total sum: %d\n\n", r)
        case <-timer.C:
            fmt.Println("the time went out for a walk")
            return
        }
    }
}

func compute(numbers <-chan int, results chan<- int) {
    sum := 0
    for {
        n := <-numbers
        sum += n
        results <- sum
    }
}

You can run thousands of goroutines and build applications with complex communication patterns by combining channels and goroutines. But always profile your solution.

And that’s what happened, but again and again, the same problem arose in a lot of different places.

But then you want to specify a timeout, deadline, or to be able to cancel the running goroutine at any time.

the pattern is extracted into the standard library

Not exactly, but I assume that’s how Context emerged. Sameer Ajmani wrote a blog post about the Context pattern:

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, cancellation signals, and deadlines across API boundaries to all the goroutines involved in handling a request. The package is publicly available as context. This article describes how to use the package and provides a complete working example.

one critical example from the standard library

Go’s standard HTTP client does not require you to specify the context when performing GET requests:

response, err := http.Get("https://...")

So, you can forget to specify timeout and then “don’t use Go’s default HTTP client (in production)".

But from Go 1.17 you can specify the timeout on the per-request basis:

client := &http.Client{Timeout: time.Minute}

context, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

req, err := http.NewRequestWithContext(context, http.MethodGet, "https://...", nil)
if err != nil {
    // ...
}

resp, err := client.Do(req)
// ... 

I hardly encourage you always specify the timeout for HTTP requests.

further readings and have a nice day 👋

I recommend you to follow the following links and carefully read them:

  1. The original story of the context package by Sameer Ajmani.
  2. Ollie writes about putting “Context” into context with providing good examples of how to support contexts in your functions.
  3. In Context Package Semantics In Go William Kennedy provides good point on tracing by using context.
  4. The documentation of the Context package itself is a good starting point to dive deeper.

At least I summarized it for myself, but I am also happy to clarify the context package to you. Have a nice day 👋