SPONSORED ADS

Golang context best practices

Last Updated Jan 14, 2023

When making a big program, especially server software, it can be helpful for a function to know more about the environment in which it is running. To enable standardized access to this type of data, Go's standard library includes a context package.

However, some found the context package tricky to use correctly. Which it shouldn't be.

Context is simple and should be used for 2 things:

  • storing request-scoped values.
  • canceling long-running operators.

And when we used context, do not include context in your struct, instead pass it as a parameter as the first param to every function.

Creating a Context

To know if you should use the context, ask yourself if the information you’re putting in there is available to the middleware chain before the request lifecycle begins. A database handle, or a logger, is generally created with the server, not with the request. So don’t use the context to pass those things around; instead, provide them to the middleware(s) that need them at construction time.

You can create a new context with:

ctx1 := context.Background()
ctx2 := context.TODO()

Both methods create an empty context. What is the difference between context todo and background? Well context.TODO() is a placeholder to make it clear that the code should be updated in the future. context.Background() is used when you simply need an empty root context with no values or cancellation.

The rule of thumb is don't store it in a struct. Context should be an interface that is passed from function to function down your call stack. Like water flowing across a river, context should be included seamlessly into your application, or to put it another way, pass a context parameter as the first param to every function on the call path between incoming and outgoing requests.

Using Data Within a Context

Which data should be placed into context? the answer is request scoped data , i.e. information that can only exist once a request has begun. Do not use context to transfer database handler or logger. This stuff should already be available before the request lifecycle starts and clearly not request-scoped data.

Btw If you have a logger that is request-scoped, it is okay to go into context.

Good examples of request-scoped data is request id, client ip address, or infomation about the data center/server that handles the request.

Basically use context to store values, like strings and data structs; avoid using it to store references, like pointers or handles.

ctx := context.Background()
ctx = context.WithValue(ctx, "beep", "boop")
fmt.Println(ctx.Value("beep")) // print boop

Another rule of thumb is if your function needs context data to work, you're doing something incorrectly. You must give that data as a parameter to the function instead of storing it in context.

Ending long-running operations using context

context.Context provides is a way to signal to any functions using it that the work has done or time out and no more work should be processed. In other words, theses functions must skip running any other database queries they haven't already run and exit.

Any blocking operator should be cancelable. If your method is blocking, it is a perfect use case for context.

In this example, we give fighting 3 seconds to run, check if ctx has been done and if true return without doing anything.

func fighting(ctx context.Context) error {
	ctx, cancel := context.WithTimeOut(ctx, 3 * time.Second)
	defer cancel()

	select {
	case <-ctx.Done():
		fmt.Println("We are already done for! beep boop.")
		return ctx.Err()
	}
}

Takeaway

To sum up, context is a tool to store data in the incoming request and goes away when the request is over. We pass it as parameter as the first param to every function. We can also give context a deadline or a time limit, and by using that context help us cancel long-running operation.

References

Hi there. Nodeepshit is a hobby website built to provide free information. There are no chargers to use the website.

If you enjoy our tutorials and examples, please consider supporting us with a cup of beer, we'll use the funds to create additional excellent tutorials.

If you don't want or unable to make a small donation please don't worry - carry on reading and enjoying the website as we explore more tutorials. Have a wonderful day!