Go Context Source Parsing

Source: Internet
Author: User
Tags comparable sprintf
This is a creation in Article, where the information may have evolved or changed.

In the previous article, Golang context, the use of the context and the application scenarios have been preliminarily understood. Then go deep into the source to learn how the context is implemented.

Emptyctx

The context package code is very small, a context.go file, a total of 480 lines of code, which also includes a large number of comments. The context package first defines a context interface:

type Context interface {    Deadline() (deadline time.Time, ok bool)    Done() <-chan struct{}    Err() error    Value(key interface{}) interface{}}

Next, you define a emptyCtx type:

// An emptyCtx is never canceled, has no values, and has no deadline. It is not// struct{}, since vars of this type must have distinct addresses.type emptyCtx int

Why is emptyCtx it called? The note says it emptyCtx cannot be canceled, has no value, and has no deadline. At the same time, emptyCtx it is not one struct{} , because this type of variable needs to have a different address.
This emptyCtx implements the Context interface:

func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {    return}func (*emptyCtx) Done() <-chan struct{} {    return nil}func (*emptyCtx) Err() error {    return nil}func (*emptyCtx) Value(key interface{}) interface{} {    return nil}func (e *emptyCtx) String() string {    switch e {    case background:        return "context.Background"    case todo:        return "context.TODO"    }    return "unknown empty Context"}

Read the above code to know why emptyCtx can not be canceled, no value, no deadline, because the above implementation is directly return. So, emptyCtx What's the use of this? Do you remember Background() the TODO() function? Yes, their interior is a pointer to the direct return emptyCtx type:

var (    background = new(emptyCtx)    todo       = new(emptyCtx))func Background() Context {    return background}func TODO() Context {    return todo}

So these two functions are generally used in the main context of functions, initialization, testing, and top-level requests. OK, keep looking down.
Since the emptyCtx type does nothing, there should be other types to implement the relevant functions, that is, cancelCtx timerCtx , valueCtx three types. Here's a look at these three types.

Cancel

As we know in the previous article, WithCancel WithTimeout WithDeadline These three methods return a function of a CancelFunc type that defines the interface within the context canceler :

type canceler interface {    cancel(removeFromParent bool, err error)    Done() <-chan struct{}}

canceleris a type of context that can be canceled directly, and will continue to look down we will find that, and not only cancelCtx timerCtx implemented the context interface (through anonymous member variables), but also implemented the canceler interface.

Cancelctx

cancelCtxDefinition of the struct body:

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}

Method Set:

Func (c *cancelctx) done () <-chan struct{} {return C.done}func (c *cancelctx) ERR () error {C.mu.lock () defer C.mu.unlock () return C.err}func (c *cancelctx) string () string {return FMT. Sprintf ("%v.withcancel", C.context)}//cancel closes C.done, cancels each of the C ' s children, and, if//removefromparent is t Rue, removes C from its parent ' s Children.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.unloc K () return//already canceled} C.err = err//Turn off C's done channel, all monitor C. The goroutine of Done () will receive the message close (C.done)//cancels the child, because it is a map structure, so the order of cancellation is not fixed for the child: = Range C.children {//N        Ote:acquiring the child's lock while holding parent ' s lock.        Child.cancel (False, err)} C.children = nil C.mu.unlock ()//Remove removed child if removefromparent from C.children { RemoveChild (C.context,c)}} 

When we call WithCancel , we actually return a cancelCtx pointer and a cancel() method:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {    c := newCancelCtx(parent)    propagateCancel(parent, &c)    return &c, func() { c.cancel(true, Canceled) }}// newCancelCtx returns an initialized cancelCtx.func newCancelCtx(parent Context) cancelCtx {    return cancelCtx{        Context: parent,        done:    make(chan struct{}),    }}

So what propagateCancel does a function do?

Find the nearest parent context that can be canceled, and put the child context into the parent. Children in//Propagatecancel arranges for child to being canceled when parent Is.func Propagatecancel (parent Context, child CA Nceler) {if parent. Done () = = Nil {return//parent is never canceled}//Determines whether the returned parent is cancelctx if p, OK: = PARENTCANCELCT X (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 ():}} ()}}//Parentcancelctx follows a chain of parent references until it finds a//*cancelctx . This function understands what each of the concrete types in this//The package represents its parent.//keeps looking up for the most recently canceled parent Contextfunc Parentcancelctx (*CANCELCTX, bool) {for { Switch c: = parent. (type)        {case *cancelctx:return C, true case *timerctx:return &c.cancelctx, True Case *valuectx:parent = C.context Default:return nil, False}}}

Timer

WithTimeoutAnd WithDeadline actually is similar, but the source code inside to help us encapsulate a bit:

Func withtimeout (parent Context, timeout time. Duration) (Context, Cancelfunc) {return Withdeadline (parent, time. Now (). ADD (timeout))}func withdeadline (parent Context, Deadline time. Time) (Context, Cancelfunc) {//Current deadline is earlier than the new deadline, return directly 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)///deadline is gone, no longer set timer if D <= 0 {C.cancel (true, deadlineexceeded)//deadline has Alrea Dy passed return C, func () {C.cancel (True, Canceled)}} c.mu.lock () defer c.mu.unlock () if c.err = = N Il {//Set D time after execution cancellation method C.timer = times. Afterfunc (D, func () {C.cancel (True, Deadlineexceeded)})} return C, func () {C.cancel (True, Canc eled)}}

timerCtxThe code is also relatively simple to implement:

type timerCtx struct {    cancelCtx    timer *time.Timer // Under cancelCtx.mu.    deadline time.Time}func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {    return c.deadline, true}func (c *timerCtx) String() string {    return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, time.Until(c.deadline))}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()}

It is important to note that timerCtx there is no direct implementation of the canceler interface, but the use of anonymous member variables, so that you do not have to implement the context interface again, but only to implement the method on demand Deadline . Method is timerCtx cancel called First cancelCtx cancel , and then the timer is stopped.

Value

First look at valueCtx the definition:

type valueCtx struct {    Context    key, val interface{}}func (c *valueCtx) String() string {    return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)}func (c *valueCtx) Value(key interface{}) interface{} {    // 找到了直接返回    if c.key == key {        return c.val    }    // 向上继续查找    return c.Context.Value(key)}

This is the simplest context implementation, in addition to the anonymous variable context, there are two additional Key,value variables to store the value. Look at WithValue the implementation:

func WithValue(parent Context, key, val interface{}) Context {    if key == nil {        panic("nil key")    }    if !reflect.TypeOf(key).Comparable() {        panic("key is not comparable")    }    return &valueCtx{parent, key, val}}

It is important to note that key cannot be nil and must be comparable, otherwise it will cause panic! Can be compared to the meaning of the key can not be a function type or Nan, or the like, specifically can look at the reflect package, here is not elaborate.

At last

The whole context look down, to the context is how to achieve also have a clear understanding, and context package has a lot of test code, very good! Recently looking at the source of go, found that really is a good learning material, how to write concise and clear code is very helpful.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.