Zwlin's Blog

Go Concurrency Patterns: Context

2020/10/20

Overview

In Go servers, each incoming request is handled in its own goroutine. Request handlers often start additional goroutines to access backends such as databases and RPC services. The set of goroutines working on a request typically needs access to request-specific values such as the identity of the end user, authorization tokens, and the request’s deadline. When a request is canceled or times out, all the goroutines working on that request should exit quickly so the system can reclaim any resources they are using.

Package context defines the Context type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.

Incoming requests to a server should create a Context, and outgoing calls to servers should accept a Context. The chain of function calls between them must propagate the Context, optionally replacing it with a derived Context created using WithCancel, WithDeadline, WithTimeout, or WithValue. When a Context is canceled, all Contexts derived from it are also canceled.

The WithCancel, WithDeadline, and WithTimeout functions take a Context (the parent) and return a derived Context (the child) and a CancelFunc. Calling the CancelFunc cancels the child and its children, removes the parent’s reference to the child, and stops any associated timers. Failing to call the CancelFunc leaks the child and its children until the parent is canceled or the timer fires. The go vet tool checks that CancelFuncs are used on all control-flow paths.

Rules

Usages

WithCancel

Introduce

WithCancel returns a copy of parent with a new Done channel. The returned context’s Done channel is closed when the returned cancel function is called or when the parent context’s Done channel is closed, whichever happens first.

Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete.

Example

 1package main
 2
 3import (
 4	"context"
 5	"fmt"
 6	"time"
 7)
 8
 9func main() {
10	gen := func(ctx context.Context) <- chan int{
11		dst := make(chan int)
12		n := 1
13		go func() {
14			for{
15				select {
16				 case <-ctx.Done():
17					 fmt.Println("gen exit")
18					 return
19				case dst<-n:
20					n++
21				}
22			}
23
24		}()
25		return dst
26	}
27
28	ctx,cancel := context.WithCancel(context.Background())
29
30	for n := range gen(ctx){
31		fmt.Println(n)
32		if n==5 {
33			break
34		}
35	}
36	cancel()
37
38	time.Sleep(500*time.Millisecond)
39}

Play

WithDeadLine

Introduce

WithDeadline returns a copy of the parent context with the deadline adjusted to be no later than d. If the parent’s deadline is already earlier than d, WithDeadline(parent, d) is semantically equivalent to parent. The returned context’s Done channel is closed when the deadline expires, when the returned cancel function is called, or when the parent context’s Done channel is closed, whichever happens first.

Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete.

Example

 1package main
 2
 3import (
 4	"context"
 5	"fmt"
 6	"time"
 7)
 8
 9const shortDuration = 1 * time.Millisecond
10
11func main() {
12	d := time.Now().Add(shortDuration)
13	ctx, cancel := context.WithDeadline(context.Background(), d)
14
15	defer cancel()
16
17	select {
18	case <-time.After(1 * time.Second):
19		fmt.Println("overslept")
20	case <-ctx.Done():
21		fmt.Println(ctx.Err())
22	}
23}

Play

WithTimeout

Introduce

WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).

Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete.

WithValue

Introduce

WithValue returns a copy of parent in which the value associated with key is val.

Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.

The provided key must be comparable and should not be of type string or any other built-in type to avoid collisions between packages using context. Users of WithValue should define their own types for keys. To avoid allocating when assigning to an interface{}, context keys often have concrete type struct{}. Alternatively, exported context key variables’ static type should be a pointer or interface.

Example

 1package main
 2
 3import (
 4	"context"
 5	"fmt"
 6	"time"
 7)
 8
 9type favContextKey string
10
11func main() {
12	k := favContextKey("language")
13	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
14	ctx2 := context.WithValue(ctx, k, "-> Route 2")
15
16	for num := range gen(ctx2){
17		fmt.Println(num)
18		if num==5{
19			break
20		}
21	}
22
23	cancel()
24	fmt.Println("Call Cancel!")
25	time.Sleep(1 * time.Second)
26}
27
28func gen(ctx context.Context) <-chan int {
29	dst := make(chan int)
30	n := 1
31	k := favContextKey("language")
32	go func() {
33		for {
34			select {
35			case <-ctx.Done():
36				fmt.Println("-> Route 1")
37				return
38			case dst <- n:
39				fmt.Println(ctx.Value(k))
40				n++
41			}
42		}
43	}()
44	return dst
45}

Play

References