Understanding of context in Go
Tags: go, webdev, beginners, programming
Published: 2023-03-12 19:50:54

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. 

```go
ctx := context.WithValue(context.Background(), "auth_token", "token")
fmt.Println(ctx.Value("auth_token")) // prints "token"
```

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.


```go
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
```
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 
```go
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

```
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i1ktzoy7zo44dplqsznx.png)

## Apply context cancel

```go
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")
        }
    }
}
```

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](https://github.com/bitethecode)