golang

Golang Context

One of the most useful features in the Go programming language is contexts. Context provides versatile features for applications such as sharing data, caller cancellations, deadlines, etc.

Context are so useful that many libraries in the go ecosystem use them. You will also find them in applications that interact with remote servers, databases, API calls and more.

For this article, we will provide a beginner introduction to the context package in Go and how to use context for various use cases.

Context WithValue

One of the main use cases of contexts in Go is sharing data or using request-scoped values. The Golang context package provides you with the WithValue function that allows you to share data.

The function syntax is as shown:

func WithValue(parent Context, key, val interface{}) Context

The function takes a context, key and value as the parameters. The function will then create a new context based on the provided parent and add a specified value to the set key.

Think of it as an internal context with a key-value pair type inside. You can then fetch or retrieve values from the type.

Consider the example below that illustrates how to use the WithValue function and retrieve values from it.

package main

import"fmt"
import"context"

funcmain() {
    ctx := context.Background()
    ctx = addValues(ctx)
    retrieveValue(ctx)
}
funcaddValues(ctx context.Context) context.Context {
return context.WithValue(ctx, "key", "value")
}
funcretrieveValue(ctx context.Context) {
    value := ctx.Value("key")
    fmt.Println(value)
}

Focus on the main function of the code above. We start by creating a new context object, ctx, with the context.Background() function.

The background function will then return an empty non-nil context with no cancellations, no values, and no deadlines.

The background function serves as the initialization for incoming Context requests.

Context Set Values

The second part of the function is the addValues() function. We take a new context and bind it to an existing context in this example. The new context holds the key to add to the context.

Notice that the WithValue function returns a copy of the parent context and does not modify the existing context.

Context Retrieve Values

The second part of the program is the retrieveValue() function. In this case, we take the context struct and retrieve the values associated with the specified key.

If the key exists within the context, the function should return the associated value.

If the key does not exist, the function should return nil. We can implement conditional logic to check if the return value is nil and return an error if true.

Context With Timeout

Another common use case of contexts is handling timeouts. For example, you may need to define a timeout in certain applications if a specified action is not performed. This is incredibly simple to implement using the WithTimeout function from the context package. Let us look at how we can implement timeout using the context package.

package main

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

funcmain() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
    ctx = addValues(ctx)
go retrieveValues(ctx)
select {
case<-ctx.Done():
        fmt.Println("Took too long!")
    }
    time.Sleep(5 * time.Second)
}

funcaddValues(ctx context.Context) context.Context {
return context.WithValue(ctx, "key", "value")
}
funcretrieveValues(ctx context.Context) {
for {
select {
case<-ctx.Done():
            fmt.Println("Timeout")
return
default:
            value := ctx.Value("key")
            fmt.Println(value)
        }
        time.Sleep(1 * time.Second)
    }
}

In this example, we define a cancel function using the context.WithTimeout function. We can then trigger the cancel function manually if we see fit.

Once the timeout value is reached, the cancel function is called and runs the specified logic.

In our retrieve value function, we continuously retrieve the value with the specified key using the for loop. We also check if the Done channel from the main context is still alive. If the timeout has not exceeded, the parent context will still be open, but once the timeout is reached, the context is canceled.

In our example, the function will run every 1 second before the timeout is reached. An example output is as shown:

value

value

value

value

value

Took too long!

Timeout

Conclusion

This was a short and fundamental tutorial discussing contexts and how to use them in your Go programs. You can check the documentation to explore more about Go contexts.

About the author

John Otieno

My name is John and am a fellow geek like you. I am passionate about all things computers from Hardware, Operating systems to Programming. My dream is to share my knowledge with the world and help out fellow geeks. Follow my content by subscribing to LinuxHint mailing list