context in Golang
Tags: go
Published: 2024-01-28 14:18:34

There are times when we are required to abandon some task, for any reason, and also want to include some helpful reason for the cancellation. 'context' can be very handy in such a scenario. Golang has a 'context' package that contains the ‘Context' type and cancellation function. 'context' package was not a standard go package initially but it is so useful that later, in version 1.7, it was included in the Golang standard package(the latest version while writing this blog is 1.21). 
The ‘Context' type is an interface with four methods Done, Err, Deadline, and value. We are not required to implement this method to use context. These functions are implemented in the package and the 'context' package exposes functions WithCancel, WithCancelCause, WithDeadline, WithDeadlineCause, WithTimeout, and WithTimeoutCause that we can use. These functions return 'CancelFunc' and 'Context'(derived context). The below code explains the usage of context and how it can be used to abandon a task.

```
func doJob(firstChildContext context.Context, secondChildContext context.Context) {
	defer wg.Done()
	for {
		select {
		case <-firstChildContext.Done():
			fmt.Println("first child exit")
			return
		default:
		}

		select {
		case <-secondChildContext.Done():
			fmt.Println("second child exit")
			return
		default:
		}
	}
}

func TryContext(flag int) {
	parentCtx, parentCancel := context.WithCancel(context.Background())
	firstChildContext, firstChildCancel := context.WithCancel(parentCtx)
	secondChildContext, secondChildCancel := context.WithCancel(parentCtx)

	go func() {
		wg.Add(1)
		doJob(firstChildContext, secondChildContext)
		fmt.Println("done with job")
	}()

	time.Sleep(5 * time.Second)
	switch flag {
	case 1:
		parentCancel()
	case 2:
		firstChildCancel()
	case 3:
		secondChildCancel()
	}
	wg.Wait()
}
```

TryContext function take a flag as input and based on flag value we cancel parent context or first context or second context. 

```
flag value 1:
first child exit
done with job

flag value 2:
first child exit
done with job

flag value 3:
second child exit
done with job
```

When the flag value is 1, we cancel the parent context and it signal all child Done channel but return from the first context. For the flag value 3, we cancel the second context and it signal the second context 'Done' and return etc.

Another version of WithCancel function is WithCancelCause that take cause as parameter in the CancelFunc and we can get the error message using the context.Cause function

```
firstChildContext, firstChildCancel := context.WithCancelCause(parentCtx)
firstChildCancel(errors.New("cancel first child"))

select {
	case <-firstChildContext.Done():
			fmt.Printf("first child exit %v\n", context.Cause(firstChildContext))
			return
	default:
}
```

From the above code it is clear that the Context.Done is a channel. Context doesn’t automatically cancel what we are doing but we get function like WithCancel or WithTimeout that provide CancelFunc and CancelFunc signal Context Done channel and, then we have a handle in code that when we receive the signal then stop what we are doing. Calling the parent CancelFunc remove the parent reference to child, and it means that garbage collector is free to garbage collect the child.

Similarly WithDeadline, WithTimeout function provide the CancelFunc but WithDeadline also signal the Done channel once the deadline cross i.e current time is greater than the time provided in deadline and WithTimeout when it timeout i.e when current time is greater than the duration plus the time when the WithTimeout function triggered.

We can use the context to signal and stop the ongoing http request because it is taking too long to respond or something went wrong and we don’t the response any more. It will help us to save the resource. I will cover this example in different blog, 'network request in golang'