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 itselftrueOr 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