DEV Community

Cover image for Using context in Go - timeout
Henryk Konsek
Henryk Konsek

Posted on

Using context in Go - timeout

#go

Go comes with a powerful concurrency control tool called 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:

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

response, err := callSlowApi()
Enter fullscreen mode Exit fullscreen mode

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:

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()
Enter fullscreen mode Exit fullscreen mode

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 you can see context timeout in action.

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

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.")
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

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.
Enter fullscreen mode Exit fullscreen mode

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:

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.
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
 
qainsights profile image
NaveenKumar Namachivayam ⚡

Thanks for the article. How do we run repetitive tasks of a func based on the duration? Please clarify.