Using context in Go - timeout
Tags: go
Published: 2021-02-18 11:07:19

Go comes with a powerful concurrency control tool called [context](https://golang.org/pkg/context). Context is primarily used when client should be able to keep a control over an execution of the library code.  

# Timeout control with context

One of the most common example of such pattern is controlling timeout of the function we are calling from our code. This is primarily useful when dealing with IO and network calls which can be blocking and unreliable by their nature.

Let's consider this example:

```go
func callSlowApi() (string, error) {
  // this is slow API call
}

response, err := callSlowApi()
```

We call some slow REST API here. Right now we cannot control the execution of the `callSlowApi()` function. In particular even if we know that API is flaky and response can take ages, we cannot control for how long our client code is ready to wait before we want to cancel our attempt to call the API.

Good library design in Go should include `context.Context` as a first parameter in the function to allow client to control the timeout:

```go
func callSlowApi(ctx context.Context) (string, error) {
  // this is slow API call, but context aware
}

// If API call takes longer than 5 seconds, return context timeout error
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second) 
response, err := callSlowApi(ctx)
defer cancel()
``` 

Now execution of `callSlowApi` function can be controlled from the client code! In the example above our client code specified that we are ready to wait 5 seconds tops before we give up on the API call.

Keep in mind that we call `defer cancel()` in our example. It is used to send a signal to the `callSlowApi` function that we are not interested in this particular call anymore and it should be canceled. It is a good practice to call cancel function after we used a context in a call to avoid context leaks.

# More examples

In [this example](https://github.com/hekonsek/quickstart-go-context/blob/main/main.go) you can see context timeout in action.

We define a "mock" task function emulating long running process that could time out:

```go
func runTask(ctx context.Context, taskTime time.Duration) {
	select {
	case <-time.After(taskTime):
		fmt.Println("Finished long running task.")
	case <-ctx.Done():
		fmt.Println("Timed out context before task is finished.")
	}
}
```

As you can see, we're passing context as the first argument to it. Thanks to this approach we can control the timeout behavior on the level of the client code calling the function. In the example below we are calling our mock task for 3 seconds and with 1 second context timeout. As a result we should see function being timed out and canceled after one second:

```go
func timeoutContextBeforeTaskIsFinished() {
	fmt.Println("This example should time out task execution:")
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel() // Always cancel() to avoid context leak

	runTask(ctx, 3*time.Second)
}

...

This example should time out task execution:
Timed out context before task is finished.
```

In the example below we are calling our mock task for 1 second and with 3 second context timeout. As a result we should see function being completed without a timeout:

```go
func finishTaskBeforeContextTimeout() {
	fmt.Println("This example should finish task execution before context timeout:")
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel() // Always cancel() to avoid context leak

	runTask(ctx, time.Second)
}

...

This example should finish task execution before context timeout:
Finished long running task.
```

# Summary

Go `context` is a powerful package that allows you to design library API with a powerful concurrency and reliability controls in place. It is highly recommended to include `context.Context` as a first parameter to the CPU/IO/network intensive functions you author. That would enable clients using your code to control how it behaves in case of reliability issues.

Context timeout is a great way to control how your client code interacts with an unreliable IO and networks.