# Notes: Golang Context

Golang Context is a tool that is used to share **request-scoped data**, **cancellation signals**, and **timeouts** or **deadlines** across API layers or processes in a program. It is one of the most important tools while working with concurrent programming in Go.

## Important Use Cases of Context

- **Request-Scoped Data:**

An example of request-scoped data would be the *body*, *headers*, or *params* of an API request. This data should be passed across different layers of API code e.g. header data containing auth token can be passed to auth middleware, and then to the respective controller using context. All this data can be wrapped in the context and context can be passed across. We can add more data in the same context for different layers.

One more example could be passing infrastructure-related details from the outermost layer in clean architecture across different layers, handler -> services -> repositories or other services, etc. We can pass DB connection, cache connection, HTTP client, etc across all these layers by using context.

- **Cancellation Signals:**

An example of cancellation signal could be when we launch multiple goroutines from the parent function/ method but we want to make sure that they all exit if the parent function terminates, maybe when a user closes the browser tab after initiating the request. There is no point in finishing other async jobs associated with it like fetching data from DB or any other API/ service.

Cancellation signals are important to avoid **goroutine leaks** as well. We should always call the cancel function at the end of the parent function/ method so that all the started goroutines exit immediately.

When a Context is canceled, all Contexts derived from it are also canceled. One important point to note is cancellation doesn't automatically stop the execution, cancel just closes the `Done` channel which we need to use to terminate processes.

- **Timeouts:**

Timeouts are important because we need to make sure any *external calls* don't block our resources for long or maybe indefinitely in worst cases. Or maybe when we are running long-running commands which may go beyond the allowed time limit.

For example, an API request should always have a timeout and that should be propagated within other processes/ goroutines or external calls initiated during the request. All the processes started this way should terminate immediately and free up all the resources as soon as the request times out.

- **Deadlines:**

Deadlines are similar to timeouts but they contain a fixed time for the deadline. This avoids running for certain operations at times when they shouldn't run e.g. let's say we have a log analysis task which is expected to be completed before a certain time as after that we get huge traffic and it may hog system resources degrading performance for clients. So at that time, we might want to notify all the helper processes to stop work and return.

## Context Tree
In practical implementations, we usually work with derived contexts. We create a *parent context* and pass it across a layer, we derive a new context with it adding some additional information and passing it again to the next layer, and so on. This way we create a tree of contexts starting from the root context which is the parent. The advantage of this structure is that we've control over the cancellation of all the contexts in one go. If the root signal closes the context, that will be propagated across all the derived contexts which can be used to terminate all the processes immediately freeing up everything. This makes context a very powerful tool in concurrent programming.

This is what `Context` interface type looks like:


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

- `Deadline()` returns the time when this context will be canceled, if any. Deadline returns `ok==false` when no deadline is set.
- `Done()` returns a channel that is closed when the context is *canceled* *or times out*. Done may return `nil` if this context can never be canceled.
- `Err()` returns the reason why the context was canceled, after `Done()` is closed. If Done is not yet closed, Err returns `nil`.
- `Value` works like a *key-value* and is used to share data.

## Creating context
We can create or derive context from existing context. Root contexts are created with `Background` or `TODO` methods, while derived contexts are created using `WithCancel`, `WithDeadline`, `WithTimeout`, or `WithValue` methods. All the derived context methods return a cancel function `CancelFunc` as well except `WithValue` as it has nothing to do with the cancellation. Calling the CancelFunc cancels the child and its children, removes the parent's reference to the child, and stops any associated timers. Failing to call the CancelFunc leaks the child and its children until the parent is canceled or the timer fires. 

Here are ways to do those:

- **context.Background() ctx Context**

This function returns an **empty context**. This should be only used usually in the main or at the top-level request handler. This can be used to derive other contexts for subsequent layers or goroutines.

`ctx, cancel := context.Background()`

- **context.TODO() ctx Context**

This function also creates an **empty context**. However, this should also be only used when you are not sure what context to use or if the function is not available to receive a context yet and will be added in the future.

`ctx, cancel := context.TODO()`

- **context.WithValue(parent Context, key, val interface{}) Context**

This function takes in a context and returns a derived context where value val is associated with key and flows through the context tree with the context. This means that once you get a context with value, any context that derives from this gets this value. This value is **immutable** and hence **thread-safe**.

The provided key must be comparable and should not be of type string or any other built-in type to avoid collisions between packages using context. Users of WithValue should define their own types for keys. To avoid allocating when assigning to an interface{}, context keys often have concrete type struct{}. Alternatively, exported context key variables' static type should be a pointer or interface.

### Example: WithValue

```
package main

import (
	"context"
	"fmt"
)

type contextKey string

func main() {
	var authToken contextKey = "auth_token"

	ctx := context.WithValue(context.Background(), authToken, "XYZ_123")

	fmt.Println(ctx.Value(authToken))
}
```

- **func WithCancel(parent Context) (ctx Context, cancel CancelFunc)**

This function takes in a parent context and returns a derived context along with a cancel function of type `CancelFunc`. In this derived context, a new `Done` channel is added which closes when the cancel function is invoked or when the parent context's Done channel is closed.

One thing to keep in mind is that we should **NEVER** pass this cancel across different functions or layers as it can cause unexpected outcomes. Function creating derived context **SHOULD** only call cancel function.

Below is an example demonstrating a goroutine leak using the `Done` channel.

### Example: WithCancel

```
package main

import (
	"context"
	"fmt"
	"math/rand"
	"time"
)

// here we are getting data from randomCharGenerator
// as soon as we get "o", we are breaking the loop
// as there is nothing else to do, main function exits
// calling cancel() function
// as soon as cancel is called , Done channel is closed
// exiting  goroutine in randomCharGenerator
// this way goroutine is exited properly without leaving it unhandled
func main() {
	rand.Seed(time.Now().UnixNano())

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // cancel when generator is closed and program exits

	for char := range randomCharGenerator(ctx) {
		generatedChar := string(char)
		fmt.Printf("%v\n", generatedChar)

		if generatedChar == "o" {
			break
		}
	}
}

// this function starts a goroutine that creates random characters
// this is a Generator pattern
func randomCharGenerator(ctx context.Context) <-chan int {
	char := make(chan int)

	seedChar := int('a')

	go func() {
		for {
			select {
			case <-ctx.Done():
				// ** this will not print as main function will exit immedietly
				fmt.Printf("Yay! we found: %v", seedChar)
				return // returning not to leak the goroutine
			case char <- seedChar:
				seedChar = 'a' + rand.Intn(26)
			}
		}
	}()

	return char
}
``` 

- **func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)**

This function returns a derived context from its parent that gets canceled when the **deadline exceeds** or cancel function is called. For example, you can create a context that will automatically get canceled at a certain time in the future and pass that around in child functions. When that context gets canceled because of the deadline running out, all the functions that got the context get notified to stop work and return. If the parent's deadline is already earlier than d, the context's Done channel is already closed.

Below is the example where we are reading a large file with a deadline time of 2 milliseconds from the current time. We'll get output for 2 milliseconds and then the context will be closed and the program exits.

### Example: WithDeadline

```
package main

import (
	"bufio"
	"context"
	"fmt"
	"log"
	"os"
	"time"
)

func main() {
	// context with deadline after 2 millisecond
	ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2*time.Millisecond))
	defer cancel()

	lineRead := make(chan string)

	var fileName = "sample-file.txt"
	file, err := os.Open(fileName)
	if err != nil {
		log.Fatalf("failed opening file: %s", err)
	}

	scanner := bufio.NewScanner(file)
	scanner.Split(bufio.ScanLines)

	// goroutine to read file line by line and passing to channel to print
	go func() {
		for scanner.Scan() {
			lineRead <- scanner.Text()
		}

		close(lineRead)
		file.Close()
	}()

outer:
	for {
		// printing file line by line until deadline is reached
		select {
		case <-ctx.Done():
			fmt.Println("process stopped. reason: ", ctx.Err())
			break outer
		case line := <-lineRead:
			fmt.Println(line)
		}
	}
}
``` 


- **func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)**

This function is similar to context.WithDeadline. The difference is that it takes in time duration as an input instead of the time object. This function returns a derived context that gets canceled if the cancel function is called or the **timeout duration** is exceeded.

WithTimeout returns `WithDeadline(parent, time.Now().Add(timeout))`.

### Example: WithTimeout

```
package main

import (
	"bufio"
	"context"
	"fmt"
	"log"
	"os"
	"time"
)

func main() {
	// context with deadline after 2 millisecond
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond)
	defer cancel()

	lineRead := make(chan string)

	var fileName = "sample-file.txt"
	file, err := os.Open(fileName)
	if err != nil {
		log.Fatalf("failed opening file: %s", err)
	}

	scanner := bufio.NewScanner(file)
	scanner.Split(bufio.ScanLines)

	// goroutine to read file line by line and passing to channel to print
	go func() {
		for scanner.Scan() {
			lineRead <- scanner.Text()
		}

		close(lineRead)
		file.Close()
	}()

outer:
	for {
		// printing file line by line until deadline is reached
		select {
		case <-ctx.Done():
			fmt.Println("process stopped. reason: ", ctx.Err())
			break outer
		case line := <-lineRead:
			fmt.Println(line)
		}
	}
}
``` 


## Important Points to Keep in Mind
- Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx.


```
func DoSomething(ctx context.Context, arg Arg) error {
	// ... use ctx ...
}
``` 
- Do not pass a nil Context, even if a function permits it. Pass context.TODO if you are unsure about which Context to use.

- Pass request-scoped data only using context. Don't pass data that should be passed using function arguments.

- Always look for goroutine leaks and use context effectively to avoid this.

- If the parent context's `Done` channel is closed, it will eventually close all the derived `Done` channels (all descendants) from it. For example,

```
package main

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

func main() {
	c := make(chan string)
	go func() {
		time.Sleep(1 * time.Second)
		c <- "one"
	}()

	ctx1 := context.Context(context.Background())

	ctx2, cancel2 := context.WithTimeout(ctx1, 2*time.Second)
	ctx3, cancel3 := context.WithTimeout(ctx2, 10*time.Second) // derives from ctx2
	ctx4, cancel4 := context.WithTimeout(ctx2, 3*time.Second)  // derives from ctx2
	ctx5, cancel5 := context.WithTimeout(ctx4, 5*time.Second)  // derives from ctx4

	cancel2()
	defer cancel3()
	defer cancel4()
	defer cancel5()

	select {
	case <-ctx3.Done():
		fmt.Println("ctx3 closed! error: ", ctx3.Err())
	case <-ctx4.Done():
		fmt.Println("ctx4 closed! error: ", ctx4.Err())
	case <-ctx5.Done():
		fmt.Println("ctx5 closed! error: ", ctx5.Err())
	case msg := <-c:
		fmt.Println("received", msg)
	}
}
``` 
Here, since we are closing `ctx2` immediately after creating other derived contexts, all other contexts also close immediately printing `ctx3`, `ctx4`, and `ctx5` closing messages randomly. `ctx5` is derived from `ctx4` which is getting closed due to cascading effect from `ctx2` closing. Try running this multiple times, you'll see the varying results.

- Contexts created using `Background` or `TODO` methods have no cancellation, value, or deadlines.

```
package main

import (
	"context"
	"fmt"
)

func main() {
	ctx := context.Background()

	_, ok := ctx.Deadline()

	if !ok {
		fmt.Println("no dealine is set")
	}

	done := ctx.Done()

	if done == nil {
		fmt.Println("channel is nil")
	}
}
``` 

**Doc Reference:**   [https://pkg.go.dev/context](https://pkg.go.dev/context) 
