Source code analysis of context package (attached)

Time:2022-5-25

Context package source code analysis

Context is equivalent to a tree structure

Finally, please answer this question: is the method in the context package thread safe?

Context package mainly has one interface and three structures

Context interface

type Context interface {
	Deadline() (deadline time.Time, ok bool)
	Done()

structural morphology

type valueCtx struct {
	Context
	key, val interface{}
}

type cancelCtx struct {
	Context

	mu       sync.Mutex            // protects following fields
	done     chan struct{}         // created lazily, closed by first cancel call
	children map[canceler]struct{} // set to nil by the first cancel call
	err      error                 // set to non-nil by the first cancel call
}

type timerCtx struct {
	cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

The context package has two root instances

package context
...
var (
	background = new(emptyCtx)
	todo       = new(emptyCtx)
)

Return by the following two methods

amongBackground()Method is automatically instantiated during initializationbackgroundObject,TODOMethod withBackground()identical

  • context.Background()

  • context.TODO()

func Background() Context {
	return background
}
Todo method is the same as background()
func TODO() Context {
	return todo
}

So what is emptyctx?

emptyCtxIs a user-defined type. The underlying type is int, which implements the four methods of the context interface and returns null or initial values

type emptyCtx int

func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
	return
}

func (*emptyCtx) Done()

Several methods commonly used in context package

  • Create a context with deallineWithDeadline(parent Context, d time.Time) (Context, CancelFunc)

  • Create context with cancel methodWithCancel(parent Context) (ctx Context, cancel CancelFunc)

  • Create context with timeoutWithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

  • Create a context with saveable key valuesWithValue(parent Context, key, val interface{}) Context

WithValue(parent Context, key, val interface{}) Context

type valueCtx struct {
	Context // equivalent to parent node
	key, val interface{}
}

func WithValue(parent Context, key, val interface{}) Context {
    //Check whether the parent node is passed
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	if key == nil {
		panic("nil key")
	}
	if !reflectlite.TypeOf(key).Comparable() {
		panic("key is not comparable")
	}
    //Bind parent node and key value pair
	return &valueCtx{parent, key, val}
}
//Override the value (key interface {}) method in the context interface
func (c *valueCtx) Value(key interface{}) interface{} {
    //First find the key pair value in your node
	if c.key == key {
		return c.val
	}
    //If it cannot be found, it recurses upward to find the value of the bound parent node in turn
	return c.Context.Value(key)
}

Write a small demo to verify it

func main() {
	ctx := context.WithValue(context.Background(), "xiaofu", "test")
	ctx1 := context.WithValue(ctx, "xiaofu1", "test1")

	fmt.Println(ctx1.Value("xiaofu1"))
	fmt.Println(ctx1.Value("xiaofu"))
    
}
//Output
test1
test

//The description is to recurse upward until the root node of the background is found

WithCancel(parent Context) (ctx Context, cancel CancelFunc)

Looking at the following code, you will find that each new context is bound to the context of the parent node

type cancelCtx struct {
	Context // parent node

	mu       sync. Mutex // lock
	Done Chan struct {} // channel, used to close
	Children map [canceller] struct {} // used to store cancelctx in child nodes
	err      error                 // set to non-nil by the first cancel call
}

//The value method is rewritten. When the key is cancelctxkey, it returns the current cancelctx. Otherwise, it keeps looking up recursively for cancelctx
func (c *cancelCtx) Value(key interface{}) interface{} {
	if key == &cancelCtxKey {
		return c
	}
	return c.Context.Value(key)
}
//Override done method
func (c *cancelCtx) Done()

WithDeadline(parent Context, d time.Time) (Context, CancelFunc)

type timerCtx struct {
	cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		// The current deadline is already sooner than the new one.
		return WithCancel(parent)
	}
    //Convert the current parent node with an intermediate object cancelctx and bind it to timectx at the same time
    //Approximately equal to temp: = cancelctx {context: parent}
    //		c := &timerCtx{
    //			cancelCtx: temp,
    //			deadline: d,
	//		}
	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) }
	}
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.err == nil {
		c.timer = time.AfterFunc(dur, func() {
			c.cancel(true, DeadlineExceeded)
		})
	}
	return c, func() { c.cancel(true, Canceled) }
}

WithTimeout(parent Context, timeout time.Duration)

//It is equivalent to delaying for a period of time based on the current dealline, so you can call the withdeadline method
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
  return WithDeadline(parent, time.Now().Add(timeout))
}

Is the method in the context package thread safe?

Is thread safe

Because each time the withxxx method is executed, a new context object will be created and the parent object will be bound.

See demo

func main() {
	ctx := context.WithValue(context.Background(), "xiaofu", "test")
	go func() {
		_ = context.WithValue(ctx, "xiaofu", "test1")
	}()
	go func() {
		_ = context.WithValue(ctx, "xiaofu", "test2")
	}()

	fmt.Println(ctx.Value("xiaofu"))
	fmt.Println(ctx.Value("xiaofu"))
	time.Sleep(3 * time.Second)
}
//Output
//test
//test