Go Context

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 26

Context - Practical Go Lessons https://www.practical-go-lessons.

com/chap-37-context

Chapter 37: Context

1 What will you learn in this chapter?


• What is context?

• What is a linked list?

• How to use the context package.

2 Technical concepts covered


• Context derivation

• Linked list

• Context key-value pair

• Cancellation

• Timeout

• Deadline

3 Introduction
This chapter is dedicated to the context package. In the first part of this chapter, we will discover what is a “context” and what its purposes
are. In the second part, we will see how we can use the context package in real-life programs.

The paper and the digital edition of this book are available here. ×
I also filmed a video course to build a real world project with Go.

4 What is Context Oriented Programming?


4.1 Definition
Context comes from the Latin word “contexo”. It means to unite, connect, link something with a group of other things. We have here the idea
that the context of something is a set of links and connections with other things. In the day to day language, we use expressions like :

• Take something out of context.

• In the context of something.

Things, actions, words have a context, have links with other things. And if we take something out of its context, we reduce it to something
that we can misunderstand. Context is an aggregation of information that improves decisions. What is part of context? Here is a partial list :

1 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

• Location

• Date

• History

• People

To better understand why context is important to us, let’s take some examples :

4.2 Context increase the understanding of an event


Imagine that during a walk, you hear a conversation between two people :

Alice
Did you see the match last week?

Bob
Yes!

Alice
After this, I’m sure they will win the next one!

Bob
Sure, I will bet one thousand on that

They speak about a “match”. A team has won a match last week and has a lot of chance to win another match next week. We have no idea
about which team and which kind of sport it is.

The context of the conversation can help us to understand it. If the conversation is happening in New York, we can guess that it has to do
with baseball or basketball because those sports are very popular there. If this conversation happens in Paris, the chance that they are
speaking about football is very high.

What we are doing here is that we add context to understand something. Here we spoke about the place. We can also add the time factor in
the context of the conversation. If we know when it happened, we will be able to browse through the week’s sports results to understand
better.

4.3 Context change behaviors


The analysis of the context of an event will change actors’ behavior. Try to answer some of those questions :

• Do you have better manners when you are in your home country and in another country?

• Do you use the same language level at the office and with your family?

• Do you dress like every day when you go to a job interview?

The response is probably “No” to those three questions. That’s because of context. We are acting differently depending on the context.
Context is affecting our behaviors. The environment has an impact on our actions and reactions.

4.4 Context in computer science


Usually, we design computer programs to execute a predefined task. The specified routines that we have implemented are always executing
the same way. The program is not changing depending on the user that is using it. Its behavior is not changing when its environment change.

The idea of context-oriented programming is to introduce variations in programs that are influenced by context. Abowd gives an interesting
definition of context in 1999 : “Context is any information that we can use to characterize the situation of an entity. An entity is a person,
place, or object that is considered relevant to the interaction between a user and an application, including the user and applications
themselves.”[@abowd1999towards].

Implicit and explicit information are the building blocks of the context. Programmers should consider context to build applications that can
adapt their behavior at runtime.

What does it mean to be intelligent? The word “intelligence” comes from the Latin root “intellego” which means to discern, to unravel, to
notice, to realize. Something is intelligent if it can discern and understand. Introducing context into applications does not make them
intelligent, but they tend to make them aware of their environment and their users.

5 The “context” package: history and use cases


5.1 Package history
This package was first developed internally by Google developers. It has been introduced in the standard library of Go. Before that, it was

2 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

available in Go sub repositories1.

5.2 Usages
The context package has two main usages :

5.2.0.1 Cancellation propagation


To understand this usage, let’s take the example of an imaginary construction company named FooBar.

Paris’s city has missioned this company to build a gigantic pool. The mayor of Paris has defended its idea among representants of the
population, and the project has been approved. The company begins to work on the project; the project manager has ordered all the raw
materials needed to build the pool. Four months have passed, but the mayor has changed, and the project has been canceled!

The project manager from FooBar is mad; the company has to cancel 156 orders. He starts to join them by phone one by one. Some of them
have also ordered raw materials from other construction companies. Everybody is suffering from this rapid situation evolution.

Now let’s imagine that the project manager does not cancel subcontractors’ orders. The other companies will produce the desired goods, but
they will not be paid. This is a big waste of resources.

As you can visualize in figure 1 cancellation of the project is propagating to all the workers that were involved indirectly. The city Council
cancels the project; the FooBar company is also canceling the orders to contractors.

In construction and other human activities, we always have a way to cancel work. We can introduce a cancellation policy into our programs
with the context package. When a request is made to a web server, we can cancel all the work chain if the client has dropped the connection!

Cancellation propagation[fig:Cancellation-propagation]

5.2.0.2 Transmission of request-scoped data along with the call stack

3 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

When a request is made to a web server, the web server function responsible for its treatment will not do the job alone. The request will pass
through a chain of functions and methods, and then the response will be sent. A single request can generate new requests to other
microservices in a microservice architecture! This chain of a function call is the “call stack”. We will see in this section why it can be useful to
transmit data along with the call stack.

We will take another example: the development of a web server for a shopping application. We have a user that interacts with our
application.

• The user will go to the login page with its web browser

• Fill it’s login details

• The web browser will send an authentication request to the server that will forward the request to the Authentication Service

• The server will build the “My Account” page (via a template, for instance) and send the user’s response.

• If the user requests the “last orders” page then, the server will need to call the Order Service to retrieve them.

An application sequence

Which data can we add to our context?

• We can keep into context the type of device that sent the request.

◦ If the device is a cellphone, we can choose to load a lightweight template to improve the user experience.

◦ The order service can also load only the last five orders to reduce the page’s rendering time.

• We can keep into the context the id of the authenticated user.

• We can also keep the IP of the incoming request.

◦ The authentication layer can use it to block suspicious activity (introduction of a blocklist, detection of false, multiple login
attempts)
• Another very common use case is to generate a single request id. The requestId is passed to each layer of the application. With the id,
the team that will cope with maintenance will have the ability to trace in logs the request.

5.2.0.3 Set deadlines and timeouts


A deadline is a time at which a task should be finished. A timeout is a very similar notion. Instead of considering a precise date and time in
the calendar, we consider a max allowed duration. We can use a context to define a time limit for long-running processes. Here is an
example:

• You develop a server, and your client has a specified timeout of 1 second.

• You can set a context with a timeout of 1 second; after this duration, you know that the client will drop the connection.

4 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

• In this case, again, we want to avoid wasting resources.

5.3 The Context interface


The context package exposes an interface that is composed of four methods :

type Context interface {


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

In the next section, we will see how to use the package

6 Linked list
The context package is built with a standard data structure: the linked list. To fully understand how context works, we first need to understand
linked lists.

A linked list is a collection of data elements. The type of data stored in the list is not restricted; it can be integers, strings, structs, floats ...etc.
Each element of the list is a node. Each node contains two things :

• The data value

• The address in memory of the next element in the list. In other words, this is a pointer to the next value.

You can see a visual representation of a linked list in the figure 3.

The list is “linked”, nodes in the list have a child (the next element in the list) and a parent (the last element in the list). Note that this remark
is not true; the first node in the list has no parent. It’s the root, the origin, the head of the list. There is another notable exception the final
node does not have any child.

Linked List[fig:Linked-List]

5 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

Linked List : pointers and values[fig:Linkedl-pointers-and-values]

7 The root context: Background


In most programs, we create a Background concept at our program’s root. For instance, in the main function that will launch our application.
To create the root context, you can use the following syntax :

ctx := context.Background()

The call to the Background() function will return a pointer to an empty context. Internally the call to Background() will create a new
context.emptyCtx .

This type is not exposed :

type emptyCtx int

The underlying type of emptyCtx is int . This type implements the four methods that are required by 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
}

Note that the emptyCtx type also implements the interface fmt.Stringer . This allows us to do a fmt.Println(ctx) :

fmt.Println(reflect.TypeOf(ctx))
// *context.emptyCtx
fmt.Println(ctx)
// context.Background

The paper and the digital edition of this book are available here. ×
I also filmed a video course to build a real world project with Go.

8 Add context to your function/methods


When your root context is created, we can pass it to functions or methods.

But before that, we have to add to our function a context parameter :

6 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

func foo1(ctx context.Context, a int) {


//...
}

In the previous listing, you note two Go idioms that are widely used inside go projects :

1. The context is the first argument of a function

2. The context argument is named ctx

9 Deriving context
We have created our root context in the previous section. This context is empty; it does nothing. What we can do is derive another child
context from our empty context :

Deriving Contexts[fig:Deriving-Contexts]

To derive a context, you can use the following functions :

• WithCancel

• WithTimeout

• WithDeadline

• WithValue

10 WithCancel
The function WithCancel takes only one argument named parent . This argument represents the context that we want to derive. We will
create a new context, and the parent context will keep a reference to this new child context.

Let’s take a look at the signature of the WithCancel function :

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

This function returns the next child context and a CancelFunc. CancelFunc is a custom type of the context package :

type CancelFunc func()

CancelFunc is a named type, its underlying type is func() . This function “tells an operation to abandon its work” (Golang sources). Calling
WithCancel will give us a way to cancel an operation. Here is how to create a derived context :

ctx, cancel := context.WithCancel(context.Background())

To cancel the operation, you need to call cancel :

cancel()

7 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

11 WithTimeout / WithDeadline
• A timeout is the maximum amount of time attributed to a process to finish normally. For any process that takes a variable amount of
time to execute, we can add a timeout, i.e., a fixed amount of time allowed to wait. Without timeouts, our application can wait
indefinitely for a process to finish.

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)

• A deadline is a specified point in time. When you set a deadline, you specify that a process will not exceed it.

deadline := time.Date(2021, 12, 12, 3, 30, 30, 30, time.UTC)


ctx, cancel := context.WithDeadline(context.Background(), deadline)

The paper and the digital edition of this book are available here. ×
I also filmed a video course to build a real world project with Go.

12 Example usage
12.1 Without context
Let’s take an example: we will design an application that has to make an HTTP request to a web server to get data and then display it to its
user. We will first consider the application without context then we will add context to it.

12.1.1 Client
package main

import (
"log"
"net/http"
)

func main() {
req, err := http.NewRequest("GET", "http://127.0.0.1:8989", nil)
if err != nil {
panic(err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
log.Println("resp received", resp)
}

We have here a simple http client. We create a GET request that will call "http://127.0.0.1:8989" . If we cannot create the request, we make
our program panic. Then we use the default HTTP client ( http.DefaultClient ) to send the request to the server (with the method Do ).

The response received is then printed to the user.

12.1.2 Server
We have set up our client. We now have to set up our fake server.

8 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

package main

import (
"fmt"
"log"
"net/http"
"time"
)

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Println("request received")
time.Sleep(time.Second * 3)
fmt.Fprintf(w, "Response") // send data to client side
log.Println("response sent")

})
err := http.ListenAndServe("127.0.0.1:8989", nil) // set listen port
if err != nil {
panic(err)
}
}

The code is simple. We first begin by setting our http handler with the function. http.HandleFunc . This function takes two parameters, the
path and the function that will respond to requests.

We wait 3 seconds with the instruction time.Sleep(time.Second * 3) then we write the response. This sleep is here to fake the time needed
for the server to answer. In this case, the response is simply "Response" .

Then we launch our server that will listen to 127.0.0.1:8989 (localhost, port 8989).

12.1.3 First test


First, we launch the server; then, we launch the client. After 3 seconds, the client has received the response.

$ go run server.go
2019/04/22 12:17:11 request received
2019/04/22 12:17:14 response sent

$ go run client.go
2019/04/22 12:17:14 resp received &{200 OK 200 HTTP/1.1 1 1 map[Content-Length:[8] Content-Type:[text/plain; charset=utf-8]
Date:[Mon, 22 Apr 2019 10:17:14 GMT]] 0xc000132180 8 [] false false map[] 0xc00011c000 <nil>}

As you can see, our client has to deal with 3 seconds latency. Let’s increase this in the server’s cod; let’s say that we now sleep for 1 minute.
Our client will wait for 1 minute; it will block our application for 1 minute.

We can note here that our client application is condemned to wait for the server even if it takes an infinite amount of time. This is not a very
good design. The user will not be happy to wait indefinitely for the application to answer. In my opinion, it is better to say to the user that
something wrong happened than to let him wait indefinitely.

12.2 Context on the client-side


We will keep the base of the code that we previously created. We will start by creating a root context :

rootCtx := context.Background()

Then we will derive this context into a new one called ctx :

ctx, cancel := context.WithTimeout(rootCtx, 50*time.Millisecond)

• The function WithTimeout takes two arguments a context and a time.Duration .

• The second argument is the timeout duration.

• Here we have set it to 50 milliseconds.

• I suggest you create a config variable in a real-world application to hold the timeout duration. By doing so, you avoid the need to
recompile your program to change the timeout.

context.WithTimeout will return:

9 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

1. the derived context

2. a cancel function

The cancel function can be called to warn a sub-process that it should abandon what it is doing. Calling cancel will free resources that are
associated with the context. To be sure that the cancel function will be called at the end of our program, we will use a defer statement :

defer cancel()

The next step consists of creating the request and attach to it our brand new context :

req, err := http.NewRequest("GET", "http://127.0.0.1:8989", nil)


if err != nil {
panic(err)
}
// add context to our request
req = req.WithContext(ctx)

The other lines are the same as the version without context.

Here is the complete client code :

// context/client-side/main.go
package main

import (
"context"
"fmt"
"net/http"
"time"
)

func main() {
rootCtx := context.Background()
req, err := http.NewRequest("GET", "http://127.0.0.1:8989", nil)
if err != nil {
panic(err)
}
// create context
ctx, cancel := context.WithTimeout(rootCtx, 50*time.Millisecond)
defer cancel()
// attach context to our request
req = req.WithContext(ctx)
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
fmt.Println("resp received", resp)
}

Now let’s test our new client. Here are the logs of the server :

2019/04/24 00:52:08 request received


2019/04/24 00:52:11 response sent

We see that we have received a request and sent a response 3 seconds later. Here are the logs of our client :

panic: Get http://127.0.0.1:8989: context deadline exceeded

We see that that http.DefaultClient.Do has returned an error .

• The text says that the deadline is exceeded.

• Our request has been canceled because our server took 3 seconds to do its job. Even if the client has canceled the request, the server
continued to do the job. We have to find a way to share that context between the client and the server.

12.3 Context on the server-side


12.3.1 Headers

10 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

An HTTP request comprises a set of Headers, a body, and a query string. When we send the request, Go will not transmit any information
about the request context.

If you want to visualize the headers of the request, you can add the following lines in the server code :

fmt.Println("headers :")
for name, headers := range r.Header {
for _, h := range headers {
fmt.Printf("%s: %s\n", name, h)
}
}

We iterate through the request’s headers with a loop and print them. Here are the headers that are transmitted with our client :

headers :
User-Agent: Go-http-client/1.1
Accept-Encoding: gzip

We have just two headers. The first one gives more information about the client used. The second informs the server that the client can
accept gzipped data. Nothing about an eventual timeout.

But if we take a look at the http.Request object, we can note that there is a method named Context() . This method will retrieve the
context of the request. If it has not been defined, it will return an empty context :

func (r *Request) Context() context.Context {


if r.ctx != nil {
return r.ctx
}
return context.Background()
}

The documentation says that “the context is canceled when the client’s connection closes”. This means that inside the go server
implementation, when the client connection is closed, the cancel function is called.

It means that inside our server, we have to listen to the channel returned by ctx.Done(). When we receive a message on that channel, we have
to stop what we are currently doing.

12.3.2 The doWork function


Let’s see how to introduce that into our server.

For the example, we will introduce a new function doWork . It will represent a compute-intensive task handled by our server. This doWork is
a placeholder for a CPU-intensive operation.

11 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

Http server handler with context activity diagram

We will launch the doWork function in a separate goroutine. This function will take as parameter the context and a channel where it will write
its result. Let’s take a look at the code of this function :

// context/server-side/main.go
//...

func doWork(ctx context.Context, resChan chan int) {


log.Println("[doWork] launch the doWork")
sum := 0
for {
log.Println("[doWork] one iteration")
time.Sleep(time.Millisecond)
select {
case <-ctx.Done():
log.Println("[doWork] ctx Done is received inside doWork")
return
default:
sum++
if sum > 1000 {
log.Println("[doWork] sum has reached 1000")
resChan <- sum
return
}
}
}

In the figure 5 you can see the activity diagram for the doWork function.

In this function, we will use a channel to communicate with the caller. We create a for loop, and inside that loop, we will put a select

12 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

statement. In this select statement, we have two cases :

• The channel returned by ctx.Done() has been closed. It means that we receive the order to finish our work

◦ In this case, we will interrupt the loop, log a message and return.
• The default case (executed if any previous case are not executed)

◦ In this default case, we will increment the sum.

◦ If the variable sum is becoming greater strictly than 1.000, we will send the result on the result channel ( resChan )

Activity diagram of the doWork function[fig:doWork-fct]

12.3.3 The server handler


Let’s see how we will use the doWork function inside our server handler :

13 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

// context/server-side/main.go
//...

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Println("[Handler] request received")
// retrieve the context of the request
rCtx := r.Context()
// create the result channel
resChan := make(chan int)
// launch the function doWork in a goroutine
go doWork(rCtx, resChan)
// Wait for
// 1. the client drops the connection.
// 2. the function doWork to finish it works
select {
case <-rCtx.Done():
log.Println("[Handler] context canceled in main handler, client has diconnected")
return
case result := <-resChan:
log.Println("[Handler] Received 1000")
log.Println("[Handler] Send response")
fmt.Fprintf(w, "Response %d", result) // send data to client side
return
}
})
err := http.ListenAndServe("127.0.0.1:8989", nil) // set listen port
if err != nil {
panic(err)
}
}

We have changed the code of the handler to use the request context. The first thing to do here is to retrieve the context of the request :

rCtx := r.Context()

Then we set up a channel of integers ( resChan ) that will allow you to communicate with the doWork function. We will launch the doWork
function in a separate goroutine.

resChan := make(chan int)


// launch the function doWork in a goroutine
go doWork(rCtx, resChan)

Then we will use a select statement to wait for two possible events :

1. The client closes the connection; consequently, the cancel channel will be closed.

2. The function doWork has finished its job. (We receive an integer from the resChan channel)

In option 1, we log a message, and then we return. When option 2 occurs, we use the result from the resChan channel, and we write it to
the response writer. Our client will receive the result of the doWork function computation.

Let’s run our server and our client. On figure 6 you can see the execution logs of the client and the server program.

You can see that the handler receives a request, then launch the doWork function. Then the handler receives the cancellation signal. This
signal is then propagated to the doWork function.

14 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

Execution logs for the client and the server[fig:Execution-logs-client-server-context]

13 WithDeadline
13.1 Definition
WithDeadline and WithTimeout are very similar. If we look at the source code of the context package, we can see that the function
WithTimeout is just a wrapper of WithDeadline :

// source : context.go (in the standard library)


func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}

If you look at the previous snippet you can see that the timeout duration is added to the current time. Let’s see the signature of the
WithDeadline function :

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

The function takes two arguments :

1. A parent context

2. A specific time.

13.2 Usage
As we said in the previous section, deadline and timeout are similar notions. A timeout is expressed as a duration, but a deadline is expressed
as a particular point in time (see figure 7)

Timeout vs Deadline[fig:Timeout-vs-Deadline]

WithDeadline can be used where you use WithTimeout. Here is an example of the standard library :

15 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

// golang standard library


// src/net/dnsclient_unix.go
// line 133

// exchange sends a query on the connection and hopes for a response.


func (r *Resolver) exchange(ctx context.Context, server string, q dnsmessage.Question, timeout time.Duration)
(dnsmessage.Parser, dnsmessage.Header, error) {
//....
for _, network := range []string{"udp", "tcp"} {
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
defer cancel()

c, err := r.dial(ctx, network, server)


if err != nil {
return dnsmessage.Parser{}, dnsmessage.Header{}, err
}
//...
}
return dnsmessage.Parser{}, dnsmessage.Header{}, errNoAnswerFromDNSServer
}

• Here the function exchange takes a context as first parameter.

• For each network (UDP or TCP), it derives the context passed as parameter.

• The input context is derived by calling context.WithDeadline . The deadline time is created by adding the timeout duration to the
current time : time.Now().Add(timeout)

• Note that immediately after creating the derived context, there is a deferred call to the cancel function returned by
context.WithDeadline . It means that when the function exchange will return the cancel function will be called.

• For instance, if the dial function returns an error for some reason, the exchange function will return, the cancel function will be called,
and the cancellation signal will be propagated to the child context.

14 Cancellation propagation
This section will dive deeper into the mechanism of cancellation propagation. Let’s take an example :

func main(){
ctx1 := context.Background()
ctx2, c := context.WithCancel(ctx1)
defer c()
}

In this small program, we begin by defining a root context : ctx1 . Then we derive this context with a call to context.WithCancel .

Go will create a new structure. The function that will be called is the following one :

// src/context/context.go

// newCancelCtx returns an initialized cancelCtx.


func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}

A cancelCtx struct is created, and our root context is embedded inside. Here the type struct cancelCtx :

// src/context/context.go

type cancelCtx struct {


Context

mu sync.Mutex // protects the following fields


done chan struct{} // created lazily, closed by the 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
}

We have five fields :

16 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

• the Context (the parent) which is an embedded filed (it has no explicit field name)

• A mutex (named mu )

• A channel named done

• A field named children that is a map. Keys are of type canceller and value of type struct{}

• And an error named err

Canceller is an interface :

// A canceler is a context type that can be canceled directly. The


// implementations are *cancelCtx and *timerCtx.
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}

A type that implements the interface canceller must implement to functions : a cancel function and a Done function.

WithCancel create a derived context and a cancel function[fig:WithCancel-create-a]

What happens when we execute the cancel function ? What happens to ctx2 ?

• The mutex ( mu ) will be locked. Hence no other goroutine will be able to modify this context.

• The channel ( done ) will be closed

• All children of ctx2 will also be canceled (in this case, we have no children...)

• The mutex will be unlocked.

14.0.0.1 Second derivation

17 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

Let’s extend our example and derive ctx2:

Derive a derived context[fig:3-contexts]

func main() {
ctx1 := context.Background()
ctx2, c2 := context.WithCancel(ctx1)
ctx3, c3 := context.WithCancel(ctx2)
//...
}

Here we create ctx3 , a new object of type cancelCtx . The children context ctx3 will be added to the parent ( ctx2 ). The parent context
ctx2 will keep memory of its children. For the moment, it only has one child ctx3 (see 9).

Now let’s see what is going on when we call the cancel function c2 .

• The mutex ( mu ) will be locked. Hence no other goroutine will be able to modify this context.

• The channel ( done ) will be closed

• All children of ctx2 will also be canceled (in this case, we have no children...)

ctx3 will be canceled with the same process

• Here ctx1 (the parent of ctx2 ) is an emptyCtx , hence ctx2 will not be removed from ctx1 .

• The mutex will be unlocked.

14.0.0.2 Third derivation

18 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

Now let’s create another derived context.

func main() {
ctx1 := context.Background()
ctx2, c2 := context.WithCancel(ctx1)
ctx3, c3 := context.WithCancel(ctx2)
ctx4, c4 := context.WithCancel(ctx3)
}

3 derived contexts[fig:3-derived-contexts]

• As you can see in figure 10 we have one root context and three descendants.

19 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

• The last one is ctx4 .

• When we call c2 it will cancel ctx2 but also its children ( ctx3 ).

• When ctx3 will be canceled it will also cancel all its children, and ctx4 will be canceled.

Cancellation propagation[fig:Cancellation-propagation-1]

The key information of this section is “when you cancel a context, the cancel operation will be propagated from parent to children”.

15 An important idiom : defer cancel()


The following two lines of code are very common :

20 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

ctx, cancel = context.WithCancel(ctx)


defer cancel()

You can encounter those lines in the standard library but also in many libraries. As soon as we derive an existing context, the cancel function
is called in a defer statement.

As we have seen before, the cancellation instruction propagates from parents to children; why do we need to call cancel explicitly? When
building a library, you are not sure that somebody will effectively execute the cancel function in a parent context. By adding the call to cancel
in a deferred statement, you ensure that cancel will be called :

• when the function returns (or reach the end of its body)

• or when the goroutine that runs the function panics.

15.1 Goroutine leak


To understand this phenomenon, we will take an example.

First, we define two functions: doSth and doSth2 . Those two functions are dummy. They are taking a context as first parameter. Then they
are waiting indefinitely for the channel returned by ctx.Done() close :

// context/goroutine-leak/main.go
// ...

func doSth2(ctx context.Context) {


select {
case <-ctx.Done():
log.Println("second goroutine return")
return
}
}

func doSth(ctx context.Context) {


select {
case <-ctx.Done():
log.Println("first goroutine return")
return
}
}

We will now use those two functions in a third function called launch :

// context/goroutine-leak/main.go
// ...

func launch() {
ctx := context.Background()
ctx, _ = context.WithCancel(ctx)
log.Println("launch first goroutine")
go doSth(ctx)
log.Println("launch second goroutine")
go doSth2(ctx)
}

In this function, we are first creating a root context (returned by context.Background ). Then we derive this root context. We call the method
WithCancel() to get a context that can be canceled.

Then we launch our two goroutines. Let’s now take a look at our main function :

// context/goroutine-leak/main.go
// ...

func main() {
log.Println("begin program")
go launch()
time.Sleep(time.Millisecond)
log.Printf("Gouroutine count: %d\n", runtime.NumGoroutine())
for {
}
}

21 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

We start the function launch in a goroutine. Then we make a small pause (1 millisecond), and we count the number of goroutines. There is a
very convenient function that is defined in the runtime package :

runtime.NumGoroutine()

The number of goroutines here should be 3: 1 main goroutine + 1 goroutine that execute doSth + 1 goroutine that execute doSth2 . If we
do not call cancel, the two last goroutines will be running indefinitely. Note that we have created another goroutine in the program: the one
that starts launch . This goroutine will not be counted because it will return almost instantaneously.

When we cancel the context, our two goroutines are returning. Hence the number of goroutines will be reduced to 1 (the main). But here, we
do not call the cancel function at all.

Here is the standard output :

2019/05/04 19:01:16 begin program


2019/05/04 19:01:16 launch first goroutine
2019/05/04 19:01:16 launch second goroutine
2019/05/04 19:01:16 Gouroutine count: 3

In the main function, we have no way to cancel our context (because it was defined inside the launch function). We have 2 leaked goroutine!
To fix that, we can just modify the function launch and add a deferred statement :

// context/goroutine-leak-fixed/main.go
// ...

func launch() {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
log.Println("launch first goroutine")
go doSth(ctx)
log.Println("launch second goroutine")
go doSth2(ctx)
}

Now let’s take a look at the logs that are obtained by running this modified version of our program :

2019/05/04 19:15:09 begin program


2019/05/04 19:15:09 launch first goroutine
2019/05/04 19:15:09 launch second goroutine
2019/05/04 19:15:09 first goroutine return
2019/05/04 19:15:09 second goroutine return
2019/05/04 19:15:09 Gouroutine count: 1

Here we have killed our two leaked goroutine!

The paper and the digital edition of this book are available here. ×
I also filmed a video course to build a real world project with Go.

16 WithValue
A Context can carry data with it. This feature is intended to be used with request-scoped data like :

• credentials (a JSON Web Token, for instance)

• request id (to trace the request in the system)

• the IP of the request

• Some headers (ex: the user agent)

16.1 Example

22 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

// context/with-value/main.go
package main

import (
"context"
"fmt"
"log"
"net/http"

uuid "github.com/satori/go.uuid"
)

func main() {
http.HandleFunc("/status", status)
err := http.ListenAndServe(":8091", nil)
if err != nil {
log.Fatal(err)
}
}

type key int

const (
requestID key = iota
jwt
)

func status(w http.ResponseWriter, req *http.Request) {


// Add request id to context
ctx := context.WithValue(req.Context(), requestID, uuid.NewV4().String())
// add credentials to context
ctx = context.WithValue(ctx, jwt, req.Header.Get("Authorization"))

upDB, err := isDatabaseUp(ctx)


if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
upAuth, err := isMonitoringUp(ctx)
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "DB up: %t | Monitoring up: %t\n", upDB, upAuth)
}

func isDatabaseUp(ctx context.Context) (bool, error) {


// retrieve the request ID value
reqID, ok := ctx.Value(requestID).(string)
if !ok {
return false, fmt.Errorf("requestID in context does not have the expected type")
}
log.Printf("req %s - checking db status", reqID)
return true, nil
}

func isMonitoringUp(ctx context.Context) (bool, error) {


// retrieve the request ID value
reqID, ok := ctx.Value(requestID).(string)
if !ok {
return false, fmt.Errorf("requestID in context does not have the expected type")
}
log.Printf("req %s - checking monitoring status", reqID)
return true, nil
}

• We have created a server that is listening on localhost:8091

• This server has one route : "/status"

23 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

• We derive the request context ( req.Context() ) with ctx := context.WithValue(req.Context(), requestID, uuid.NewV4().String())

◦ We add a key-value pair to the context: the requestID


• Then we renew the operation. We add a new key pair to hold the request credentials : ctx = context.WithValue(ctx, jwt,
req.Header.Get("Authorization"))

The context values are then accessible into isMonitoringUp and isMonitoringUp :

reqID, ok := ctx.Value(requestID).(string)
if !ok {
return false, fmt.Errorf("requestID in context does not have the expected type")
}

16.2 Key type


Here is the header of the method WithValue :

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

The arguments key and val are of type interface{} . In other words, they can have any type. Only one restriction should be respected,
the type of key should be comparable.

• We can share a context across several packages.

• You might want to restrict the access to context values outside the package where values were added.

• To do that, you can create an unexported type

• All keys will be of this type.

• We will define keys globally inside the package :

type key int

const (
requestID key = iota
jwt
)

In the previous example, we created a type key with underlying type int (comparable). Then we defined two unexported global constants.
Those constants are then used to add a value and to retrieve a value from the context :

// add a value
ctx := context.WithValue(req.Context(), requestID, uuid.NewV4().String())

// get a value
reqID, ok := ctx.Value(requestID).(string)

16.2.0.1 Missing value & type expected different than the actual type
• When the key-value pair is not found in the context, ctx.Value will return nil .

• That’s why we are making a type assertion: to protect us against missing values or values that have not the required type.

16.3 Questions
1. What is the difference between a deadline and a timeout ?

2. Fill in the blanks. Each node of a linked list contains a ____ value and an ____.

3. How to create an empty context?

4. With which method(s) can you derive a context?

5. When the key-value pair is not found in the context, what is returned by ctx.Value(key) ?

16.4 Answers
1. What is the difference between a deadline and a timeout?

1. Deadline: a precise point in time. ex: 12 December 2027

24 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

2. Timeout: a duration. ex: 12 seconds

2. Fill in the blanks. Each node of a linked list contains a ____ value and an ____.

1. Each node of a linked list contains a data value and an the address in memory of the next element

2. Except for the final node. It has no address set.

3. How to create an empty context?

1. ctx := context.Background()
4. With which method(s) can you derive a context?

1. WithCancel

2. WithTimeout

3. WithDeadline

4. WithValue

5. When the key-value pair is not found in the context, what is returned by ctx.Value(key)

1. nil

17 Key Takeaways
• Context is a package from the standard library

• We can use context to :

◦ Cancellation propagation (ex: cancel all the work chain if the API consumer has dropped the connection)

◦ Transmission of request-scoped data along with the call stack.

◦ Set deadlines and timeouts.

• Deadline = a precise point in time

• Timeout = a duration

• Internally the package context is built with a linked list.

• A linked list is a collection of data. Each node of a linked list contains a data value and an address in memory of the next element.

• To create an empty context, use :

ctx := context.Background()

• We can derive each context with the following methods :

◦ WithCancel : ctx, cancel := context.WithCancel(context.Background())

◦ WithTimeout : ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)

◦ WithDeadline : ctx, cancel := context.WithDeadline(context.Background(), deadline)

◦ WithValue : ctx := context.WithValue(context.Background(),"key","value")

• By deriving a context, you create a node in the linked list of contexts.

• When you cancel a context, the cancel operation will be propagated from parent to children contexts.

• Context can carry request-scoped values.

• To add a value, derive the context like this : ctx := context.WithValue(req.Context(), requestID, uuid.NewV4().String())

• To retrieve a value from context use this syntax : reqID, ok := ctx.Value(requestID).(string)

• When no values are retrieved with the given key ctx.Value will return nil

• Usually, a global unexported variable/constant of an unexported type is used as key.

25 of 26 02/01/2023, 02:22
Context - Practical Go Lessons https://www.practical-go-lessons.com/chap-37-context

1. https://github.com/golang/go/wiki/SubRepositories↩

Bibliography
• [abowd1999towards] Abowd, Gregory D, Anind K Dey, Peter J Brown, Nigel Davies, Mark Smith, and Pete Steggles. 1999. “Towards a Better
Understanding of Context and Context-Awareness.” In International Symposium on Handheld and Ubiquitous Computing, 304–7. Springer.

Previous Next

Program Profiling Generics

Table of contents

Did you spot an error ? Want to give me feedback ? Here is the feedback page! ×

Newsletter:
Like what you read ? Subscribe to the newsletter.

I will keep you informed about the book updates.

@ my@email.com

Practical Go Lessons
By Maximilien Andile
Copyright (c) 2023
Follow me Contents
Posts
Book
Support the author Video Tutorial

About
The author
Legal Notice
Feedback
Buy paper or digital copy
Terms and Conditions

26 of 26 02/01/2023, 02:22

You might also like