Insights

10 Golang Context Best Practices

Contexts are an important part of the Go programming language, but they can be tricky to use. Here are 10 best practices for using contexts in Go.

The Context package in Golang provides a way to carry deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.

In this article, we will discuss 10 best practices for using Context in Golang. By following these best practices, you can avoid common mistakes and make your code more robust.

1. Use context.WithCancel, WithDeadline and WithTimeout instead of NewContext

When you create a new context.Context object using NewContext, it doesn’t have a parent context, so it can’t be canceled by its parent. This means that if you use NewContext to create a context.Context object and pass it to a goroutine, that goroutine will never be able to cancel itself using that context.Context object.

However, if you use context.WithCancel, WithDeadline or WithTimeout to create a context.Context object, it will have a parent context (the context.Background() object), so it can be canceled by its parent.

This is important because it means that if you use context.WithCancel, WithDeadline or WithTimeout to create a context.Context object and pass it to a goroutine, that goroutine will be able to cancel itself using that context.Context object.

So, always use context.WithCancel, WithDeadline or WithTimeout instead of NewContext when creating a new context.Context object.

2. Don’t pass nil Context to functions that take a Context

When you pass nil Context to a function, it means that the function will not be able to timeout or cancel. This can lead to unexpected behavior, such as the function running forever or blocking indefinitely.

It’s always best to use the context.Background() function to create a new Context if you don’t have one already. This ensures that your code is safe and won’t cause any unexpected behavior.

3. Always check the error returned from CancelFunc

When a context is canceled, all of its children are also canceled. This means that if you have a long-running process that creates many sub-contexts, it’s possible for one of those sub-contexts to be canceled while the others are still running.

If you don’t check the error returned from CancelFunc, you might not realize that a context has been canceled and your program could continue running indefinitely.

Always checking the error returned from CancelFunc ensures that you can gracefully handle context cancellation in your programs.

4. Use background for root Contexts

When a Context is created, it is assigned a parent. The root Context of a program should have background as its parent, to avoid leaking resources. If you create a Context with nil as its parent, it will inherit from the root Context, which has background as its parent.

If you use context.Background() when creating your root Context, you are guaranteed that it will have background as its parent. This is important because it means that your root Context will not be leaked if it is never canceled.

5. Avoid passing long-lived contexts down the call stack

When a context is canceled, all of the goroutines that are running in that context are also canceled. So if you have a long-lived context and pass it down to many different goroutines, then when that context is canceled, all of those goroutines will be canceled as well.

This can lead to some very unexpected behavior, so it’s important to be aware of it. The best way to avoid this problem is to create new contexts for each goroutine that you create, and only pass in the context that is relevant to that goroutine.

6. Prefer parent to child when choosing which Context to cancel

When a Context is canceled, all of its children are also canceled. So if you have a parent Context and a child Context, and you cancel the child Context, the parent Context will also be canceled.

However, if you cancel the parent Context, the child Context will not be canceled. Therefore, it’s generally best to cancel the parent Context rather than the child Context.

There are exceptions to this rule, of course. For example, if you have a long-running task in the child Context that you want to cancel immediately, then it makes sense to cancel the child Context. But in general, prefer canceling the parent Context over the child Context.

7. Add deadlines to your Contexts

When you create a Context with a deadline, it means that your code will automatically cancel the Context if it takes too long to finish. This is important because it prevents your code from running forever and using up resources unnecessarily.

It’s also worth noting that when you add a deadline to a Context, it doesn’t just apply to the Context itself. It also applies to any child Contexts that are created from it. So if you have a Context with a deadline of 10 seconds, and you create a child Context from it, that child Context will also have a deadline of 10 seconds.

8. Pass request scoped values via Context

When a request comes in, it’s important to have all the information about that request readily available. This includes things like the request ID, the user ID, any headers, and so on.

One way to do this is to create a struct that contains all of these values and then pass it around to all the functions that need it. However, this can quickly become unwieldy, especially if you have a lot of values that need to be passed around.

A much better way to do this is to use Context. With Context, you can create a context.Context object that contains all the values you need and then pass it to all the functions that need it. This is much cleaner and more efficient than passing around a struct.

It’s also worth noting that Context is safe for concurrent use, so you don’t need to worry about race conditions.

9. Do not store Contexts inside struct types

When a Context is stored inside a struct type, it can be passed around to other goroutines and the original context might no longer be valid in those goroutines. This can lead to subtle bugs that are hard to debug.

A better way to store Contexts is using a global variable or passing it explicitly to each function that needs it.

10. Use Go 1.7’s new APIs for cancelling http requests

When an http request is cancelled, any associated resources (such as open files or database connections) are also freed up. This ensures that your program doesn’t leak resources when requests are cancelled.

The new APIs make it easy to cancel an http request. Simply use the WithCancel function to create a new Context with a cancellation function, and then pass that Context to the http.Request function. When you’re ready to cancel the request, just call the cancellation function.

If you’re using an older version of Go, you can still use the old context.Context type, but you’ll need to use the CancelRequest function instead of WithCancel.

Previous

10 Socket.IO Best Practices

Back to Insights
Next

10 Git Repository Structure Best Practices