Usage Scenarios
There are three main usage scenarios for Context
- Passing timeout information, which is most used.
- Passing signals, used for message notification, handling multi-process communication
- Passing data, commonly used in the framework layer trace-id, metadata
Let’s take an example of etcd watch to get a better understanding.
|
|
First the child ctx
and cancel
functions are generated based on the parent ctx
passed in as an argument. Then watch
passes in child ctx
, and if parent ctx
is cascaded by cancel
, child ctx
is cascaded by cancel
, rch
is closed by etcd
, and then the for
loop goes to the select
logic, at which point child ctx
is cancelled. So <-ctx.Done()
takes effect and the watch
function returns
The context makes it possible for multiple goroutines to collaborate and manage timeouts, which greatly simplifies development work. This is the beauty of Go
Principle
Context
is an interface
Deadline
ctx returns this value if it is closed at a certain point in time. Otherwise ok is falseDone
returns a channel that will be closed if it times out or is cancelled, enabling message communication.Err
Returns an error if the current ctx has timed out or been cancelled.Value
Returns a value based on a key, similar to a dictionary.
Current implementations are emptyCtx
, valueCtx
, cancelCtx
, timerCtx
. Child Context can be generated based on a Parent.
After multiple derivations, ctx is a multinomial tree-like structure. When ctx-1 is canceled, the whole tree with ctx-1 as root is cascaded and canceled, but the original root, ctx2 ctx3, is not affected.
|
|
First detect the done channel, if someone is listening, then close it, then all goroutines waiting for this ctx will receive the message.
Then it iterates through the children map, canceling all children in turn, similar to the prior order traversal of a tree. Finally, removeFromParent
removes itself from the parent node.
A few questions
prints Ctx
Using WithCancel
as an example, you can see that child also references parent, and with the propagateCancel
function, parent also references child (when parent is of type cancelCtx).
|
|
If ctx is printed at this point, the String()
method will be called recursively, and the key/value
will be printed. If the value is non-thread-safe at this point, such as map, it will raise a concurrent read and write panic
.
This case is an implementation of the http standard library server.go:2906 line of code that saves the http server to the ctx.
|
|
The final call to the business layer code passes the ctx to the user.
|
|
If you print ctx at this point, you will print the http srv structure, which contains the map. If you are interested, you can do an experiment and take the ab pressure test to easily reproduce it.
|
|
Also note that go has since made a partial fix for this, which has somewhat solved the problem. But also remember not to print ctx.
Key/Value type is not safe
Strongly do not use Context to pass too much data, here you can see that key
/ value
types are interface{}
, compile-time can not determine the type, run-time need to assert, there are performance and security issues
closes the underlying connection
The Context
timeout triggers the http pool to close the underlying connection, resulting in frequent connection rebuilding.
The problem is that if the application layer reads it and then drops it, the connection is still available, but if the OS tcp stack handles the useless data, it closes directly. grpc doesn’t have this problem because it is multiplexed and each request is a virtual stream, so if it times out, it just closes the stream, not the underlying tcp connection
Doubly linked list
When Context
is derived from more layers, it forms a doubly linked list, and key
/ value
fetching is likely to degenerate into an O(N) operation, which is very slow
|
|
Whenever a key
/ value
is added a new valueCtx
is generated, and when queried, if the current ctx does not have a key
, the c.Context
is queried recursively.
timeout in advance
When the call stack is deep, it is easy to generate this situation when multiple people cooperate. In fact, still do not understand how ctx cancel works, asynchronous go out of the business logic needs to be based on context.Background()
and then derive child ctx, otherwise it will return early timeout.
Another point that is easy to ignore is that by default grpc
will pass through the timeout, for example, if the entry A service calls B and the timeout is set to 2s, if B uses the same Context
to call downstream C, then the timeout will be subtracted from B’s own processing time. If the link is long, it is likely to time out by the time it reaches the G service
Passing the timeout can release the resources earlier, otherwise the backend is still processing the request after the entry timeout.
Custom Ctx
The reason why custom Context
is very unconventional is that it is handled differently in the source code.
|
|
As you can see from the source code, there are two ways for parent
to refer to child
, the official cancelCtx
type is saved with map. But the unofficial one needs to open goroutine
to monitor it. The business code is already full of goroutines
, so using them unchecked will only increase the burden on the system.
Suggestions for use
Finally, to summarize a few principles of context usage.
- Don’t use
WithValue
to carry business data except at the framework level, this type isinterface{}
, which cannot be determined at compile time, andassert
has overhead at runtime. If you do carry it, use thread-safe data - Be sure not to print
Context
, especially if it’s derived from thehttp
standard library, who knows what’s in it Context
is usually passed as the first argument to a function, but if the life cycle ofContext
is equivalent to that of a structure, it’s fine to treat it as a structure member- Don’t customize user-level
Context
if possible, unless the benefits are huge - Asynchronous goroutine logic uses
Context
to be clear about who is still holding it and whether it will time out early, especially when calling rpc, db, redis. - The derived child ctx must be used with defer cancel() to free up resources.