DEV Community

Joonhyeok Ahn (Joon)
Joonhyeok Ahn (Joon)

Posted on

Understanding of context in Go

Context is a standard interface allowing you to carry request-scoped values, cancelation signals, and deadlines across API boundaries and between processes. The concept seems straightforward, but it wasn't obvious when I tried applying it to the codebase. After experimenting, I’m sharing what I learned about the context in Go today.

The Usage

Request scoped data

With context, you can pass around request scoped data such as body, headers, or params to downstream. For example, context can contain auth token and this can be passed to middleware or controller for further usage.

ctx := context.WithValue(context.Background(), "auth_token", "token")
fmt.Println(ctx.Value("auth_token")) // prints "token"
Enter fullscreen mode Exit fullscreen mode

You can add key value pair to context with WithValue function. When we pass down this derived context, any context from the derived one can see this value.

Cancellation

This function returns a derived context from the context passed. A new Done channel is added, which is closed when the cancel function is invoked or the parent context Done channel is closed.

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
Enter fullscreen mode Exit fullscreen mode

The general rule is only the function that creates derived context should call the cancel function to avoid any unexpected outcomes. Also, always call cancel to release the children's resources held by the parent context. Otherwise, it can lead to a possible memory leak.

Timeout/Deadlines

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

Enter fullscreen mode Exit fullscreen mode

Under the hood, timeout and deadlines serve to avoid holding resources for a while. When timeout exceeds or context is canceled, the Done channel will be closed. If that happens, ctx.Err will not be nil. So you can find the error cause by a switch statement.

How to create context?

There are a few ways to create context.

  1. context.Background() this will return the empty context. Most times you can use this as a parent context that will be passed down.
  2. context.TODO() this will also return the empty context. Yet, you can use it as a placeholder when you are not sure and want to defer decision.
  3. r.Context() this will return the request's context. I use it for most cases since I'm handling API heavily.

What is context tree?

In Go, we usually use derived context. The typical workflow is it starts from the parent context, a new context is derived with added additional information, and is passed down to the next layer. So, essentially it makes a context tree. This can be beneficial as you can cancel all the context at once.

Image description

Apply context cancel

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
   ctx, cancel := context.WithTimeout(context.Background(),time.Duration(3*time.Second))

    go func(ctx context.Context) {
        // simulate a process that takes 4 second to complete
        time.Sleep(4 * time.Second)
    }(ctx)

    select {
    case <-ctx.Done():
        switch ctx.Err() {
        case context.DeadlineExceeded:
            fmt.Println("context timeout exceeded")
        case context.Canceled:
            fmt.Println("context cancelled by force. whole process is complete")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Let me explain the code snippet above. The main program creates a new context with a cancelation signal that will be triggered after 3 seconds. Then it starts a long-running go routine that listens for the context to be canceled. As the go routine takes more than time out configured, the go routine will be canceled. As the main program is listening to the context channel, it will print out “context timeout exceeded" after the context is canceled.

A few notes

  • If background workers share context for context in the HTTP cycle, this can be canceled when the context from the request is canceled. If you don't want this, you should create a brand new context for background processing using context.Background()
  • Pass request-scoped data only using context. Other data should be passed via function arguments.
  • Be careful of goroutine leaks

Conclusion

Context library is so nice and simple, we have to use it with caution as it can result in unexpected behavior in the codebase. I wish you understand better the context after reading this.

If you like this follow @bitethecode and Github

Top comments (0)