Understanding Context in Golang. Demystifying the ctx variable. | by Jason Ngan | Mar, 2022

Demystifying the ctx variable

Person handing baton to another during race

If you are new to Golang, chances are, you have seen the context module almost everywhere but at times find it confounding, if not abstract.

The fact is, the context module is ubiquitous for a reason. It plays a vital role in maintaining your application performance.

In this blog post, I will save you the trouble of going through the source code, and tell you everything you need to know about the context module!

Let’s get started!

Imagine being the person taking orders in a restaurant.

When an order arrives, you delegate it to one of your many chefs.

What would you do if the customer suddenly decides to walk away?

Without a doubt, you will stop your chef from further processing the order to prevent any waste of ingredients!

That’s what the context module does!

It’s an argument passed to your functions and Goroutines and allows you to stop them promptly should you not require them anymore.

Typical usage of the context module is when a client terminates the connection with a server.

What if the termination occurs while the server is in the middle of some heavy lifting work or database query?

The context module allows these processes to be stopped instantly as soon as they are not further in need.

The usage of the context module boils down to three primary parts

  • Listening to a cancellation event
  • Emitting cancellation event
  • Passing request scope data

Let’s discuss them separately.

The Context type is nothing but an interface that implements four simple functions.

For now, let’s focus on the first two, Done() and Err().

The Done() function returns a channel that receives an empty struct when a context is canceled.

The Err() function returns a non-nil error in the event of cancellation otherwise, it returns a nil value.

Using these functions, listening to a cancellation event becomes trivial.

In the example above, we simulated a web service handler.

We used time.After() to simulate a function that takes two seconds to process a request.

If the context is canceled within two seconds, the ctx.Done() channel receives an empty struct. The second case will be executed and the function exits.

You can fire up this code on your local. Once up, visit localhost:8000 on your browser, then close it within two seconds. Observe your terminal and see what happens.

Alternatively, you can check for errors from ctx.Err() before executing some critical logic.

If the context is canceled, the function above halts and returns.

The context module provides three functions that return a CancelFunc .

Calling the cancelFunc emits an empty struct to the ctx.Done() channel and notifies downstream functions that are listening to it.

Before we dive into them, let’s first talk about the context tree and the root context.

As you call the WithX functions, they accept a parent context and return a new copy of the parent with a new Done channel.

In the example above, we created a multiple context tree.

When we call cancelFunc1we will cancel child1Ctx and child3Ctxwhile leaving child2Ctx unaffected.

Since the functions require a parent context as an argument, the context module offers two simple functions to create a root context.

These functions output an empty context that does nothing AT ALL. It cannot be canceled nor carry a value.

Their primary purpose is to serve as a root context that will later be passed to any of the WithX functions to create a cancellable context.

The WithCancel function takes in a parent context and returns a cancellable context and a cancel function.

If the databaseQuery returns an error, the cancel function will be invoked. operation1 will then be notified via ctx.Done() and exits gracefully.

You can find the cancellation reasons in ctx.Err().

WithTimeout allows you to specify a timeout duration and automatically cancels the context if the duration exceeds.

In the example above, the context will be canceled automatically after three seconds.

Hence, if the database query doesn’t succeed before that, the handler will exit and return.

Alternatively, you can cancel the context manually via the cancel function.

The WithDeadline function accepts a specific timeout time instead of a duration. Other than that, it works exactly similar as WithTimeout

The context in the example above will be canceled automatically after three seconds.

As we usually pass the ctx variable across functions, request scope data can tag along this variable using the WithValue function.

Considering an application that involves multiple function calls, we can pass a traceID to these functions for monitoring and logging via the ctx variable.

The WithValue function adds a value to a key in the ctx variable while the Value function retrieves a given key’s value.

Though handy, the context module is often misused and can easily introduce bugs to your application.

Before we end the post, let’s talk about some essential practices.

When you spawn a new cancellable context via the WithCancel function, the module will

  • Spawn a new Goroutine in the background to propagate the cancellation event to all children if the cancel function is invoked
  • Keep track of all the children contexts in the struct of the parent context

If a function returns without canceling the context, the Goroutine and the child contexts will remain in the memory indefinitely causing a memory leak.

This also applies to WithTimeout and WithDeadline except, these functions automatically cancel the context when the deadline is exceeded.

However, it’s still a best practice to defer the cancellation for any of the WithX functions.

It’s easy to assume that we are overwriting key1 with value2 in the last function call.

However, that’s not the case.

The WithValue function takes in a parent context and returns a context copy. Hence, instead of overwriting the value, it’s creating a new copy with a new key-value pair.

Hence, you should restrict the use of WithValue to limited request scope data.

Passing function arguments or values ‚Äč‚Äčthat will be mutated later on will result in the creation of multiple context variables and causing your memory usage to increase significantly.

That’s it about the context module!

The gist above sums up everything the context module has to offer!

Hopefully you find this post helpful and with that, I shall see you next time, Ciao!

Leave a Comment