This is a creation in
Article, where the information may have evolved or changed.
Cause
Recently, learning the Golang framework found that many places used the context concept, such as GRPC request ETCD access and many other places. In the spirit of understanding the way to the realization of the momentum, decided to study the principle of implementation.
Use
- Generally used in the framework of GRPC, such as setting the time-out period, such as
ctx, cancel := context.WithTimeout(context.Background(), 2 * time.Second)dial, err := grpc.DialContext(ctx, etcdAddr, grpc.WithInsecure(),grpc.WithBalancer(balancer)) cancel()
Here a timeout context is obtained by Withtimeout to Grpc. Dialcontext as a parameter, the context itself has a timer timer that automatically cancel the context and close the done Chan inside the context when the timer timer time is up. In general, the method of using CTX as a parameter will check that done Chan once found Chan closed, then should think that the operation needs to end, and thus return the error (this error is also within the context of err, within the context of cancel)
- When using the context in your own program, the internal implementation method is similar to the following
func doSomething(ctx Context) error{ //go .....doSomethingLong...... select{ case <-ctx.Done(): return ctx.Err() case err <- somethingChan: return err }}
Approximate structure
//Context接口type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{}}//主要暴露的方法func WithCancel(parent Context) (ctx Context, cancel CancelFunc)func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)func WithValue(parent Context, key, val interface{}) Contextfunc WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
The above is the Contxt interface structure and the most commonly exposed methods,
//内部interfacetype canceler interface { cancel(removeFromParent bool, err error) Done() <-chan struct{}}//内部重要structtype timerCtx struct { cancelCtx timer *time.Timer // Under cancelCtx.mu. deadline time.Time}type cancelCtx struct { Context done chan struct{} // closed by the first cancel call. mu sync.Mutex children map[canceler]struct{} // set to nil by the first cancel call err error // set to non-nil by the first cancel call}type valueCtx struct { Context key, val interface{}}
Canceler interface
The Cancel method cancels the context directly, and the approximate implementation method is to place the Err field of the cancelctx, which means that the context is invalidated when the Err field is not empty;
The Done method returns whether the channel is completed and the context is completed successfully
Timerctx
The actual structure returned by Withdeadline and Withtimeout (when the parent is not invalidated), which contains a cancelctx. The context of CANCELCTX is the real parent of Timerctx.
Implements the interface Canceler.
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() if c.timer != nil { c.timer.Stop() c.timer = nil } c.mu.Unlock()}
Cancelctx
The corresponding withcancel contains the context for its parent context.
Implements the interface Canceler
func (c *cancelCtx) cancel(removeFromParent bool, err error) { if err == nil { panic("context: internal error: missing cancel error") } c.mu.Lock() if c.err != nil { c.mu.Unlock() return // already canceled } c.err = err close(c.done) 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() if removeFromParent { removeChild(c.Context, c) }}
There are three main things to do
- Err was placed
- Close Chan Done
- Recursive call to sub-context cancel method
Valuectx
Corresponds to Withvalue. Contains the context for its parent context. The simplest of VALUECTX logic is the addition of a pair of key-value pairs, which mainly provide the role of context variable preservation. The value method can recursively look up the value corresponding to key. See the value method below
func (c *valueCtx) Value(key interface{}) interface{} { if c.key == key { return c.val } return c.Context.Value(key)}
The main concrete implementation
Func Withdeadline (parent Context, Deadline time. Time) (Context, cancelfunc) {if cur, OK: = parent. Deadline (); OK && cur. Before (deadline) {//The current deadline is already sooner than the new one. return Withcancel (Parent)} c: = &timerctx{cancelctx:newcancelctx (parent), Deadline:deadline, } propagatecancel (parent, c) d: = time. Until (Deadline) if D <= 0 {C.cancel (true, deadlineexceeded)//deadline has already passed return C, Func () {C.cancel (True, Canceled)}} c.mu.lock () defer c.mu.unlock () if C.err = nil {C.timer = time. Afterfunc (D, func () {C.cancel (True, Deadlineexceeded)})} return C, func () {C.cancel (True, Canc eled)}}func Withcancel (the parent Context) (CTX context, cancel Cancelfunc) {c: = Newcancelctx (parent) Propagatecancel (parent, &c) return &c, func () {C.cancel (True, Canceled)}}func propagatecancel (parent Context, child canCeler) {if parent. Done () = = Nil {return//parent is never canceled} if p, OK: = Parentcancelctx (parent); OK {p.mu.lock () if p.err! = Nil {//parent has already been canceled Child.cancel (FA LSE, P.ERR)} else {if P.children = = Nil {P.children = make (map[canceler]struct{}) } P.children[child] = struct{}{}} p.mu.unlock ()} else {go func () { Select {Case <-parent. Done (): Child.cancel (False, parent.) ERR ()) Case <-child. Done ():}} ()}}func removechild (parent Context, child Canceler) {p, OK: = Parentcancelctx (Parent If!ok {return} p.mu.lock () if p.children! = nil {Delete (P.children, child)} p.mu.un Lock ()}
First determine whether the parent itself has expired, if it expires, only return CANCELCTX, because it has expired, there is no need to use TIMERCTX set the expiration time and so on. Otherwise, create the timerctx.
Then find out the nearest parent cancelctx, if there is a timerctx that is placed on his child, does not exist on a goroutine polling waits for the parent to complete, and once the parent completes, cancel the timerctx.
Based on the child's expiration date, if it has expired, cancel the timerctx directly and remove it from the parent, preventing the resource from accumulating, such as not expiring setting the timer expires when the Timerctx is cancel off.
The Cancel method has the parameter removefromparent, which indicates whether to remove the Canceler from the parent context.
Because only Cancelctx has a child field, you need to find the nearest cancel parent to remove the child.
Summarize
A loop of code to look down, the principle and structure actually understand, but actually there are some small details of the implementation of the people around for a long, such as
-
Cancelctx's child put all the Canceler interface map, because the implementation of the Canceler interface structure to implement the Cancel method.
-
Timerctx's Cancel method calls C.cancelctx.cancel (false, Err) first, and then calls RemoveChild (C.cancelctx.context, C, in the Judgment removefromparent). ). Because the direct call to C.cancelctx.canel (true, err) shows no purpose to remove C, because this is removed from C.cancelctx's child, C is in the child of C.cancelctx.context.
Concluding impressions
The original has not written this article, wrote an article only to find the time. And write these two is not good, but do write can deepen understanding, find superficial look at the code did not find the place, or very good. Keep going.