Exploring Context Package in Go: A Comprehensive Guide
Listen to what you know instead of what you fear.
— Richard Bach
Medium Link: Context Package You Should Know in Golang | by Wesley Wei | Jun, 2024 | Programmer’s Career
Hello, here is Wesley, Today’s article is about golang-context in Go. Without further ado, let’s get started.💪
1.1 Context Introduction
From the versions of the context
package on context package versions - context - Go Packages, we can see that Go’s standard library introduced the context
package in version go1.7beta1. It defines the Context
type, which represents the context of a goroutine, can be used to transmit contextual information, such as cancellation signals, timeouts, deadlines, and key-value pairs.
We can understand it as a container for the context in which a program runs. Of course, this is just some basic understanding, and the Go documentation has a more critical statement:
The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines.
This statement suggests a close relationship between context and goroutines. Let’s continue to explore further.
1.2 Why Context
As we all know, a seemingly simple business request in the server-side is actually very complex. Multiple goroutines are working simultaneously to complete it. Some go to Redis to retrieve metadata, some go to S3 to retrieve specific data, and some go to call the interfaces of downstream microservices.
This requires that requests have a hierarchical relationship, making it easy to perform timeouts and resource recovery, for example, we do not want the downstream goroutines to continue executing tasks when the upstream request stops, as this will waste resources, resource leakage, or even service avalanche.
In Go’s unique design, context is introduced to solve this kind of problem (which is rarely seen in other programming languages).
Through context and goroutines’ cooperation, we can achieve the effect introduced in Go Concurrency Patterns: Context - The Go Programming Language in the introduction.
When a request is canceled or times out, all the goroutines working on that request should exit quickly so the system can reclaim any resources they are using.
As for how to implement it, we can find the answer in the source code analysis.
1.3 Creating and Using Context
We can create a context
using the functions provided by the context
package:
1. context.Background()
and context.TODO()
context.Background()
returns an empty context
that does not contain any data, does not support cancellation operations, and does not have a deadline set. We typically use context.Background()
in the main function, initialization, and testing code.
On the other hand, context.TODO()
returns an empty context
as well, but in terms of semantics, we choose to use context.TODO()
when we are unsure which context
to use.
1 | ctx := context.Background() |
WithCancel Example:
1 | package main |
WithDeadline, WithTimeout, WithValue, etc. Can be used as shown in the official examples Context Examples.
1.4 Context Source Code Analysis
The focus here is to analyze the example of
WithCancel
in go1.22.4, specifically in the WithCancel function.
1 | func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { |
We can break down the execution steps as follows:
- Create a
cancelCtx
object, which serves as the childcontext
- Then call
propagateCancel
to establish the relationship between the parent and childcontexts
. This way, when the parentcontext
is canceled, the childcontext
will also be canceled. - Return the child
context
object and the cancel function for the sub-tree.
cancelCtx
and propagateCancel()
1 | type cancelCtx struct { |
The following function has three different cases related to the parent context:
- When
parent.Done() == nil
, or in other words,parent
will not trigger the cancel event, the current function will simply return; - When the child context’s inheritance chain contains a cancellable context, it will check if
parent
has already triggered the cancel signal;- If already canceled, the child will be immediately canceled;
- If not canceled, the child will be added to
parent
‘schildren
list, waiting forparent
to release the cancel signal;
- When the parent context is a developer-defined type, implements the
context.Context
interface, and returns a non-empty channel in theDone()
method;- Run a new Goroutine simultaneously monitoring
parent.Done()
andchild.Done()
Channels; - In
parent.Done()
close, callchild.cancel
to cancel the child context;
- Run a new Goroutine simultaneously monitoring
The purpose of context.propagateCancel
is to synchronize cancel and end signals between parent
and child
, ensuring that when parent
is canceled, the child
also receives the corresponding signal, avoiding inconsistent states.
cancel()
The most important method is context.cancelCtx.cancel
, which closes the Channel in the context and synchronizes the cancel signal to all child contexts:
1 | func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) { |
From the source code, we can see that the cancel
method can be called multiple times and is idempotent. In summary, we can create a simple diagram to illustrate the cancel process:
Other Methods
context.WithDeadline
and context.WithTimeout
have similar logic but also handle timers. They create a context.timerCtx
during the creation process and check the deadline date of the parent context against the current date. They then create a timer using time.AfterFunc
, and when the time exceeds the deadline, they call context.timerCtx.cancel
to synchronize the cancel signal.
XXXCause
further extends the error-related process, solving some annoying problems such as unfriendly error returns, inability to customize errors, and difficulty debugging when encountering issues that are hard to trace.
1 | ctx, cancel := context.WithCancelCause(parent) |
For more details, refer to the following article: Context cancel cause in Go 1.20. Go 1.20 introduced two new functions in… | by Peter Gillich | Dev Genius
1.5 Best Practices for context
Here are some best practices to consider:
- Do not store context in structs: Context was designed to facilitate passing between functions, not to store state information. Therefore, it’s best to maintain its fluidity rather than storing it in structs.
- Use context’s Value method sparingly: Although the Value method of context is very useful in some scenarios, overuse of this feature can make the code harder to maintain, especially when context is heavily used.
- Follow the lifecycle management rules of the context package: Generally, context should be created by the outermost function that initiates the request and passed down through the call chain. If your function needs to run in a subprocess and may need to be canceled or timed out, consider receiving a context parameter. Avoid storing and using context in global variables.
- Call cancel functions using defer statements: When using the
WithCancel
,WithTimeout
, orWithDeadline
functions to return cancel functions, you should call them to release related resources. The best practice is to call these cancel functions through defer statements before your function returns. - Create new contexts as much as possible: Instead of reusing existing contexts, this allows each request to have a context with a clear lifecycle.
1.6 Summary
The Go official recommendation is to use Context as the first parameter of functions. If we want to control the cancellation actions of all coroutines, almost all functions should have a Context parameter, and context will spread like a virus everywhere.
Additionally, functions like WithCancel
actually create a series of linked list nodes. We know that operations on linked lists usually have O(n) complexity, and the more child nodes there are, the lower the efficiency will be.
So, what problem does the context package solve? The answer is: cancelation
. It helps us better manage parallel control and better manage goroutine
overuse. Although it is not perfect, it does solve the problem in a simple way.
More Series Articles about You Should Know In Golang:
https://wesley-wei.medium.com/list/you-should-know-in-golang-e9491363cd9a
And I’m Wesley, delighted to share knowledge from the world of programming. don’t forget to follow me, it would be a great help to me. See you in the next article. 👋
中文文章: https://programmerscareer.com/zh-cn/golang-context/
Author: Wesley Wei – Twitter Wesley Wei – Medium
Note: Originally written at https://programmerscareer.com/golang-context/ at 2024-06-13 01:12. If you choose to repost or use this article, please cite the original source.
Comments