Golang context understanding
Article edition
Golang context understanding
The context is introduced
In the Go server, each incoming request is processed in its own Goroutine. Request handlers typically start other Goroutines to access the back end, such as databases and RPC services. The Goroutine collection that processes the request typically requires access to request-specific values, such as the identity of the end user, authorization token, and the duration of the request. When a request is cancelled or times out, all goroutines handling the request should exit quickly so that the system can reclaim any resources they are using.
GO officials have developed the Context package, which easily passes the value of the request range, cancellation signal, and expiration date across API boundaries to all goroutines handling the request. The package can be used publicly as a context.
Context solution scenario
The scene of a
Use Conext to solve this problem, and when the request ends, stop what you’re doing
func main(a) {
http.HandleFunc("/hello".func(writer http.ResponseWriter, request *http.Request) {
log.Println("I received the request.")
var wg sync.WaitGroup
wg.Add(1)
// Do A thing
go func(a) {
defer wg.Done()
select {
case <-request.Context().Done(): // Determine whether the request is finished
// do something
log.Println("I'm glad it's over.")}} ()// do B thing
// do C things
wg.Wait()
})
fmt.Println(http.ListenAndServe(": 8080".nil))}Copy the code
Context is used to address the functions of exit notification between Goroutines, metadata passing (synchronous request for specific data, cancellation signals, and deadline for processing requests)
How do YOU use context
Use context to notify the child collaboration
// Use context to notify the subassociation of completion
func main(a) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// 500ms
go handle3(ctx, 3*time.Second)
// If none of the cases in the select are met, wait until a case meets the criteria
select {
case <-ctx.Done():
// main Context Deadline exceeded Indicates an error caused by the expiration time
fmt.Println("main", ctx.Err())
}
// Avoid the program that prematurely exits resulting in the child coordination not being printed
time.Sleep(time.Second * 1)}// Use context to synchronize signals
func handle3(ctx context.Context, duration time.Duration) {
select {
case <-ctx.Done():
fmt.Println("handle", ctx.Err())
case <-time.After(duration):
fmt.Println("process request with", duration)
}
}
Copy the code
Use context to pass data to child associators (keys must be comparable)
func main(a) {
ctx, cancel := context.WithCancel(context.Background())
valueCtx := context.WithValue(ctx, "traceId"."abc123-abc")
go func(ctx context.Context) {
select {
case <-ctx.Done():
fmt.Println(ctx.Value("traceId"))
}
}(valueCtx)
time.Sleep(1 * time.Second)
cancel()
time.Sleep(1 * time.Second)
}
Copy the code
Some important data structures in the context package
The context interface
type Context interface {
// Returns the set deadline and whether deadline is set
Deadline() (deadline time.Time, ok bool)
// A read-only chan is returned when the context is cancelled or deadline is reached
Done() <-chan struct{}
// Return the reason for the cancellation
Err() error
// Returns the value in context, which will recursively fetch the data based on the key
Value(key interface{}) interface{}}Copy the code
The main method that context provides externally
// The parent context used for contex is an empty context
func Background(a) Context {}
// When you are not sure which context is appropriate, use it as a placeholder
func TODO(a) Context {}
// Create a context that can cancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {}
// Create a context with an expiration date
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {}
// WithTimeout basically calls WithDeadline to create a context with a timeout
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {}
// Create a context to store k-v pairs
func WithValue(parent Context, key, val interface{}) Context {}
Copy the code
cancelCtx
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done chan struct{} // This is like a semaphore that tells the context that it needs to cancel execution
children map[canceler]struct{} // To store the child context
err error // set to non-nil by the first cancel call
}
Copy the code
Question answer
How does the context tell the subassociation to end?
-
Here’s an example of WitchCancel()
Canceler [canceler]struct{
2. When cancel triggers a call, it is critical to iterate over the children of the current context, cancel recursively and cancel the child context done close (similar to a semaphore).
Delete the current node from the parent context
-
The context tree
/ / the key
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
ifc.err ! =nil {
c.mu.Unlock()
return // already canceled
}
c.err = err
// Once c. one closes, the corresponding context receives a signal and, based on the received signal, performs other operations (prematurely terminating the business process)
if c.done == nil {
c.done = closedchan
} else {
close(c.done)
}
// Loop recursively to call the children's subcontext to cancel
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
// Remove yourself from the parent context
if removeFromParent {
removeChild(c.Context, c)
}
}
Copy the code
Why return a cancel to the outside?
When panic occurs or certain conditions are met, we can be more flexible to defer cancel() or cancel() and inform the subcontext to exit the program. By using defer cancel(), we avoid being unable to cancel the program when panic occurs Child context, causing a memory leak (WitchCancel context)
What is the meaning and difference between True and false preached by Canel?
- The external provides cancel, which is triggered by the external itself
true
Or pass ture (remove itself from the parent context if cancellation conditions are met or cancellation is explicitly required) because the deadline is up.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
return &c, func(a) { c.cancel(true, Canceled) }
}
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
// If d is less than the current time, the deadline has been passed
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func(a) { c.cancel(false, Canceled) }
}
if c.err == nil {
// A timer is started and duR is performed
c.timer = time.AfterFunc(dur, func(a) {
c.cancel(true, DeadlineExceeded)
})
}
return c, func(a) { c.cancel(true, Canceled) }
}
Copy the code
- Cancel subcontext logic of Cancel, which passes false in canceling children
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
ifc.err ! =nil {
c.mu.Unlock()
return // already canceled
}
c.err = err
// Once c. one closes, the corresponding context receives a signal and, based on the received signal, performs other operations (prematurely terminating the business process)
if c.done == nil {
c.done = closedchan
} else {
close(c.done)
}
for child := range c.children {
// Notice that false is passed here
child.cancel(false, err)
}
// Set children in this context to nil after canceling above
c.children = nil
c.mu.Unlock()
// Cancels itself from the parent context when an external call is made or when true is passed on due date
if removeFromParent {
removeChild(c.Context, c)
}
}
Copy the code
- Look at the picture analysis
-
When the child. Cancel (true), err)
1. Ctx11 calls Cancel (true,err)
2. Call child.cancel(true, err) over children of CTx11
3, cancel children, G1, G2, G3 recursively, close own done and set own chidren=nil
RemoveChild (c.ontext, c) removes itself (G1, G2, G3) from the parent (ctx11) context
Children =nil, then delete ctx1 from parent context
-
When the child. Cancel (false, err)
1. Ctx11 calls Cancel (true,err)
Ctx11: cancel(false, err)
3, cancel children, G1, G2, G3 recursively, close own done and set own chidren=nil
Children =nil, then delete ctx1 from the parent context
-
conclusion
Cancel (true), err) is executed one more step than child.cancel(false), err), removeChild(c.text, c), C. children =nil completely removes the child context (G1, G2, G3) from children
True cancel(true), err) is called only when an external call to Cancel or cancel is due, iterating over children, passing the cancel signal, and deleting itself from the parent context
How do you determine a cooperative timeout?
- The practice of WithDeadline
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
// A timer will be created and started, AfterFunc will be executed when the duR time expires, thus' c.ancel (true, DeadlineExceeded) '
c.timer = time.AfterFunc(dur, func(a) {
c.cancel(true, DeadlineExceeded)
})
}
return c, func(a) { c.cancel(true, Canceled) }
}
Copy the code
- Verify that AfterFunc created and started a timer
// After 2s, print Hello
func main(a) {
go func(a) {
AfterFunc starts when you create the time.afterfunc timer
_ = time.AfterFunc(time.Second*2.func(a) {
fmt.Println("hello")
})
}()
time.Sleep(5 * time.Second)
}
Copy the code
How do I add a child context to the children of the parent’s context
func propagateCancel(parent Context, child canceler) {
Done is initialized when called (except for the top-level Context, which is an empty implementation).
done := parent.Done()
if done == nil {
return // parent is never canceled
}
select {
case <-done:
// parent is already canceled
child.cancel(false, parent.Err())
return
default:}CancelCtx, because only cancelCtx has children
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
// err is not nil if the parent context has been cancelled
ifp.err ! =nil {
// parent has already been canceled
child.cancel(false, p.err)
} else {
// Construct the children map and mount child to the parent context
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
atomic.AddInt32(&goroutines, +1)
// removing case1 may cause cancellation signals from the parent node to never be transmitted to the child node
If the parent node is not cancelled consistently, then this goroutine will leak
go func(a) {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
Copy the code
How is the timeCtx cancel cancelled?
func (c *timerCtx) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(false, err)
if removeFromParent {
// Remove this timerCtx from its parent cancelCtx's children.
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
/* c := &timerCtx{ cancelCtx: newCancelCtx(parent), deadline: d, } propagateCancel(parent, c) dur := time.Until(d) if dur <= 0 { c.cancel(true, DeadlineExceeded) // deadline has already passed return c, func() { c.cancel(false, Canceled) } } */
// If the timer is nil, the timer is nil. =nil
The call of // cancel may be directly called externally, before the timer triggers execution, or it may be triggered by the timer after the deadline
ifc.timer ! =nil {
// Stop the timer, the external timer has already cancelled, in case the timer triggers cancle again after the deadline
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}
Copy the code
ParentCancelCtx (parent) parentCancelCtx(parent)
Refer to the link
Decrypt the Go language context in depth