Timeout-Context-In-Go

Go ahead , make my day.

In concurrent programming with Golang, the context package is a powerful tool to manage operations like timeouts, cancelation, deadlines, etc.

Among these operations, context with timeout is mainly used when we want to make an external request, such as a network request or a database request. I will show you how to use it to timeout a goroutine in this post.

Let’s first see a simple example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

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

func main() {
// Channel used to receive the result from doSomething function
ch := make(chan string, 1)

// Create a context with a timeout of 5 seconds
ctxTimeout, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()

// Start the doSomething function
go doSomething(ctxTimeout, ch)

select {
case <-ctxTimeout.Done():
fmt.Printf("Context cancelled: %v\n", ctxTimeout.Err())
case result := <-ch:
fmt.Printf("Received: %s\n", result)
}
}

func doSomething(ctx context.Context, ch chan string) {
fmt.Println("doSomething Sleeping...")
time.Sleep(time.Second * 5)
fmt.Println("doSomething Wake up...")
ch <- "Did Something"
}

Okay, what are we doing here?

1. Timeout Context

Creating a timeout context is very easy. We use the function WithTimeout from the context package.

The following example defines a timeout context that will be canceled after 3 seconds.

1
2
ctxTimeout, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()

Here, the WithTimeout takes a parent context and a duration parameter and returns a child context with a deadline set to the specified duration.

The parent context is returned by function Background. It is a non-nil, empty Context and is typically used by the main function as the top-level Context for incoming requests.

2. Long Waiting Function

We define a function that will execute in a separate goroutine. It will send the result to a predefined channel when finished.

1
2
3
4
5
6
func doSomething(ctx context.Context, ch chan string) {
fmt.Println("doSomething Sleeping...")
time.Sleep(time.Second * 5)
fmt.Println("doSomething Wake up...")
ch <- "Did Something"
}

The following is the predefined buffered channel.

1
ch := make(chan string, 1)

How to execute this function? It’s easy!

1
go doSomething(ctxTimeout, ch)

3. Waiting Orchestration

We wait for the result from the predefined result or from the timeout context channel in the main function.

The context will automatically signal to the ctxTimeout.Done channel if the timeout is reached. Otherwise, we will receive the result from the ch channel.

1
2
3
4
5
6
select {
case <-ctxTimeout.Done():
fmt.Printf("Context cancelled: %v\n", ctxTimeout.Err())
case result := <-ch:
fmt.Printf("Received: %s\n", result)
}

User Cases

To better understand the context, let’s look at some real-world use cases.

Mongo

1
2
3
4
5
6
opts := options.Client()
client, _ := mongo.Connect(context.TODO(), opts)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

client.Database("db").Collection("collection").InsertOne(ctx, bson.M{"x": 1})

http.Get() timeout per request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ctx, cancel := context.WithTimeout(context.Background(), time.Microsecond*200)
defer cancel()

req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://google.com", nil)
if err != nil {
log.Fatalf("Error: %v", err)
return
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatalf("Error: %v", err)
return
}
fmt.Println(resp.StatusCode)

https://medium.com/geekculture/timeout-context-in-go-e88af0abd08d