# First Look at Go Context -- (From Draft)

# Go Context

- use context to gather additional information about the environment they're being executed in.

## lets get into it :) 

```go
package main

import "context"
import "fmt"

func doSomething(ctx context.Context){
	fmt.Println("Doing something", ctx)
}

func main(){
	ctx := context.TODO()
	doSomething(ctx)
}

```

- Here we have used, context.TODO function, on of two ways to create an empty (or starting) context.
- Question is can i pass anything here? -- No

```go
ctx:= context.JPT();
```

you can't do this :D

- In the function accepting context, it is recommended to pass context as the first argument. As followed in go standard library.

## context.Background()

- creates empty context like context.TODO
- designed to be used where you intend to start a known context.
- both function do the same: they return empty context that can be used as a context.Context
- diff? -- how you signal your intent ?
  -- if you are not sure what to use, context.Background() is the default option.

### Using data within a context

- ability to access data stored inside the context
- by adding data to the context and passing the context from function to function, each layer of the program can add additional information about what's happening.

**Add Value**

- To add the new value to the context use `context.withValue` function.
- parameters of `context.WithValue`

```bash
1. parent i.e `context.Context`
2. key
3. value
```

- the key and Value can be of any type
- Return a new `context.Context` with the value added to it.

**Accessing Value** with `Value`

syntax: `ctx.Value("myKey")`

```go

package main

import "context"
import "fmt"

func doSomething(ctx context.Context){
	fmt.Printf("Doing something : %s \n", ctx.Value("myKey"))

    // context.WithValue(ctx, "fun", "doSomething")
}

func main(){
	ctx := context.Background()
	ctx = context.WithValue(ctx, "myKey", "myValue")
	doSomething(ctx)

    // funcUsed := ctx.Value("fun");
    // fmt.Println("Function Used ", funcUsed)
}

```

Here i have tried whether i can get back the value set by the child function to its parent function or caller function.

If you see the commented code, we are trying that.
Answer is NO, you will get `Nil` as the answer.

If you want to return the value then you can provide values in another context. I am not sure about its use case but you can simply use something like this.

```go
package main

import "context"
import "fmt"

func doSomething(ctx context.Context) context.Context {
	fmt.Printf("Doing something : %s \n", ctx.Value("myKey"))
	aCtx := context.WithValue(ctx, "fun", "doSomething")
	return aCtx;
}

func main(){
	ctx := context.Background()
	ctx = context.WithValue(ctx, "myKey", "myValue")
	funCtx:= doSomething(ctx)
	fmt.Printf("Which function I have called : %s \n", funCtx.Value("fun"))
}

```

Reason:

1. Values stored in the specific context are immutable, i.e they can't be changed.
2. When calling `context.Value`, you pass the parent context and get new one. This function didn't modify the context you have provided.
3. It wrapped our parent context inside another one with the new value.

To explain this, lets have another example from article

```go
package main

import "context"
import "fmt"

func doSomething(ctx context.Context) {
	fmt.Printf("doSomething: myKey's value is %s\n", ctx.Value("myKey"))

	anotherCtx := context.WithValue(ctx, "myKey", "anotherValue")
	doAnother(anotherCtx)

	fmt.Printf("doSomething: myKey's value is %s\n", ctx.Value("myKey"))
}

func doAnother(ctx context.Context) {
	fmt.Printf("doAnother: myKey's value is %s\n", ctx.Value("myKey"))
}

func main(){
	ctx := context.Background()
	ctx = context.WithValue(ctx, "myKey", "myValue")
	doSomething(ctx)
}
```

And the possible output will be like:

```bash
doSomething: myKey's value is myValue
doAnother: myKey's value is anotherValue
doSomething: myKey's value is myValue
```

## Overuse

- context can be powerful tool to use but it shouldn't be used as the replacement for the arguments in the function.
- Rule of thumb is you should always use arguments to the function for business logic.
- Additional information can be passed with context.

# Ending a Context

- Another powerful feature of the context is that signaling function that context is ended and anything you (function) is doing related to context can stop or ended.
- `context.Context` provides a method called `Done` that can be checked to see whether a context has ended or not.
- This method return `channel` that is closed when the context is done, and any functions watching for it to be closed will know they should consider their execution context completed and should stop any processing related to that context.
- `Done` method works because no values are ever written to its channel.
- Periodically checking if done can help you ...
- `Done` channel and `select` statement goes even further by allowing you to send data to or receive data from other channels simultaneously.

- select statement doc: https://go.dev/ref/spec#Select_statements

**Select**

- select statement in Go is used to allow a program to try reading from or writing to a number of channels all at the same time.
- Only one channel operation happens per `select` statement, but when performed in a loop, the program can do a number of channel operations when one becomes available.
- Each case statement can be either a channel read or write operation, and select statement will block until one of the case statements can be executed.
- you can use default statement that will be executed immediately if none of the other case statements can be executed.
- its works similar to `switch` statement but for channels.

For example

```go

ctx:= context.Background()
resultCh := make(chan *WorkResult)

for {
	select {
		case <- ctx.Done():
			// The context is over, stop
			return
		case result := <- resultCh:
			// process the result received
	}
}
```

Lets see the full example here:

```go
package main

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

func doSomething(ctx context.Context) {
	ctx, cancelCtx := context.WithCancel(ctx)

	printCh := make(chan int)
	go doAnother(ctx, printCh)

	for num := 1; num <= 3; num++ {
		printCh <- num
	}

	cancelCtx()

	time.Sleep(100 * time.Millisecond)

	fmt.Printf("doSomething: finished\n")
}

func doAnother(ctx context.Context, printCh <-chan int) {
	for {
		select {
		case <-ctx.Done():
			if err := ctx.Err(); err != nil {
				fmt.Printf("doAnother err: %s\n", err)
			}
			fmt.Printf("doAnother: finished\n")
			return
		case num := <-printCh:
			fmt.Printf("doAnother: %d\n", num)
		}
	}
}

func main(){
	ctx := context.Background()
	ctx = context.WithValue(ctx, "myKey", "myValue")
	doSomething(ctx)
}
```

## Context Deadline

```go

func doSomething(ctx context.Context) {
	deadline := time.Now().Add(1500 * time.Millisecond)
	ctx, cancelCtx := context.WithDeadline(ctx, deadline)
	defer cancelCtx()

	printCh := make(chan int)
	go doAnother(ctx, printCh)

	for num := 1; num <= 3; num++ {
		select {
		case printCh <- num:
			time.Sleep(1 * time.Second)
		case <-ctx.Done():
			break
		}
	}

	cancelCtx()

	time.Sleep(100 * time.Millisecond)

	fmt.Printf("doSomething: finished\n")
}

```

## Context with TimeLimit

```go
func doSomething(ctx context.Context) {
	ctx, cancelCtx := context.WithTimeout(ctx, 1500*time.Millisecond)
	defer cancelCtx()

}
```

## Refs:

1. digital ocean: https://www.digitalocean.com/community/tutorials/how-to-use-contexts-in-go
2. Reddit thread: https://www.reddit.com/r/golang/comments/y2voom/what_are_the_use_cases_for_contexts/

## Few AI feedbacks:

Q1. how to avoid race condition while writing database by goroutines with a simple example please.

Answer with its explanation:

- who cares about explanation, I only care about the code :)
- Seems pretty fair.

```go
package main

import (
	"fmt"
	"sync"
	"time"
)

type Database struct {
	data map[string]string
	mu   sync.Mutex
}

func main() {
	db := &Database{
		data: make(map[string]string),
	}

	// Create a wait group to wait for all goroutines to finish
	var wg sync.WaitGroup

	// Number of goroutines
	numWorkers := 10

	// Add the number of goroutines to the wait group
	wg.Add(numWorkers)

	for i := 0; i < numWorkers; i++ {
		go func(index int) {
			defer wg.Done()

			// Simulate some work
			time.Sleep(time.Millisecond)

			// Acquire a lock before accessing the database
			db.mu.Lock()
			defer db.mu.Unlock()

			// Perform the database write
			key := fmt.Sprintf("key-%d", index)
			value := fmt.Sprintf("value-%d", index)
			db.data[key] = value

			fmt.Printf("Goroutine %d wrote to the database: %s -> %s\n", index, key, value)
		}(i)
	}

	// Wait for all goroutines to finish
	wg.Wait()

	// Print the final state of the database
	fmt.Println("\nFinal state of the database:")
	for key, value := range db.data {
		fmt.Printf("%s -> %s\n", key, value)
	}
}

```

Q2. how can i use context for the same use case?

```go
package main

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

type Database struct {
	data map[string]string
	mu   sync.Mutex
}

func main() {
	db := &Database{
		data: make(map[string]string),
	}

	// Create a wait group to wait for all goroutines to finish
	var wg sync.WaitGroup

	// Number of goroutines
	numWorkers := 10

	// Add the number of goroutines to the wait group
	wg.Add(numWorkers)

	// Create a context with cancellation
	ctx, cancel := context.WithCancel(context.Background())

	for i := 0; i < numWorkers; i++ {
		go func(ctx context.Context, index int) {
			defer wg.Done()

			// Simulate some work
			time.Sleep(time.Millisecond)

			// Acquire a lock before accessing the database
			db.mu.Lock()
			defer db.mu.Unlock()

			// Check if the context is canceled
			select {
			case <-ctx.Done():
				fmt.Printf("Goroutine %d canceled\n", index)
				return
			default:
				// Perform the database write
				key := fmt.Sprintf("key-%d", index)
				value := fmt.Sprintf("value-%d", index)
				db.data[key] = value
				fmt.Printf("Goroutine %d wrote to the database: %s -> %s\n", index, key, value)
			}
		}(ctx, i)
	}

	// Cancel the context after a certain duration
	go func() {
		time.Sleep(5 * time.Millisecond)
		cancel()
	}()

	// Wait for all goroutines to finish or context cancellation
	wg.Wait()

	// Print the final state of the database
	fmt.Println("\nFinal state of the database:")
	for key, value := range db.data {
		fmt.Printf("%s -> %s\n", key, value)
	}
}

```
