Golang Context

Learn via video courses
Topics Covered

Overview

Go's context package can be useful for working with slow processes and APIs, especially in production-grade systems that handle web requests. Where you might want to tell every goroutine to stop what they're doing and come back. Here is a little article that will show you the basics of using it in applications and learn more about golang context.

Before Context

Let's first understand some pre-existing issues that the context package aims to resolve Why Do We Need the golang Module?

Problems:

  • Assume you start a function and need to send some common parameters to the remaining functions. You cannot give these common parameters as arguments to all downstream functions.
  • For solving this, You created a goroutine, which in turn activated more goroutines, and so on. Imagine the task you were working on is no longer required. Then, how should all child goroutines be informed to gently terminate so that resources can be freed up?
  • A task should be executed within a time limit, say 3 seconds. Otherwise, it should gently stop or return.
  • A task should be completed by a specific deadline, such as per the specific time provided. If it is not completed, it should peacefully stop and return.

As you've seen, many of the issues listed above include HTTP requests, but they also affect a wide range of many other circumstances.

For a More Relatable Example,

Consider yourself as the person taking orders in a restaurant. When an order comes in, you assign it to one of your many cooks. What would you do if the consumer suddenly decided to leave?

Without a doubt, you will prevent your chef from continuing to process the order to avoid any waste of ingredients! That is exactly what the context module does!

Introduction

Golang Context is a standard package in Golang that is used to access or share data, for example when we have multiple functions and we want to share data between them, we can use context. We can do so by using context.WithValue, which is a function that is used to create a new context we will discuss it in the next section.

For example, if you are doing a web request or running a system command and suddenly it goes down and there is no indication or acknowledgment provided by the server at that time it ends up increasing the load and degrading the performance.

To prevent this, when the client disconnects, the server can stop processing the request because it is aware of the context of the request, such as the client's connection state. By doing this, a busy server can free up valuable computational resources to handle another client's request of another client. This kind of information can be useful when performing database calls by implementing context which works like a timeout or deadline or a channel to indicate stop working and return.

Features of Context:

  • It is used to abort signals, and deadlines across API boundaries to all goroutines involved in processing the request.
  • You can design parcels that can behave as ticking time bombs, stopping the execution of your code if it goes beyond its predetermined deadline or timeout value.

Prerequisites

  • Working knowledge of goroutines and channels, as described in the lesson How to Run Multiple Functions Concurrently in Go.
  • Familiarity with utilizing dates and times in Go

When to use Context?

We use context when we have data shared throughout our application's scope. For instance:

  • Request ids for goroutine and function calls that are a component of an HTTP request call
  • Information retrieval or data recovery or data fetching errors when working with a database.
  • When you wish to stop an operation in the middle of it - A HTTP request should be ended since the client has disconnected.
  • When you wish to stop an operation before a specific time - for example, a cron that needs to be aborted in 5 minutes if it is not done.
  • Obtain additional information about the environment they're running in, and usually pass that information to the functions they also call.

Creating a Context

The two ways to create a Context:

  • context.Background()
  • context.TODO()

Both functions return a non-nil, empty context. The only time TODO is used instead of Background is when the implementation is unclear or the context is not yet known.

Let's begin by analyzing this first aspect of functionality, the ability to store additional data.

Create main.go file :

Use the go run command on the main.go file:

Output for the above code:

Run the code!

Explanation of the above code:

  • In this code we have used the context.TODO()function which is used to create an empty (or starting) context. We can use this when we don't know which context we want to use particularly. It will act like a placeholder.
  • We added a method called "func()" that takes context. Although it doesn't completely use it yet, it only accepts context as a parameter. The variable is called ctx and is used to store context values.   

The root method in the context package is called Background:

  • It contains no values.
  • It is never terminated
  • It has no set deadline.

The root contains all other context methods as its child.

We can add this context to any function using context. Let's imagine we have a function called myfunc; then, as an argument, we can add the context:

We have four options for making our context stop the program execution if a long period has passed:

  1. context.Value
  2. context.WithCancel
  3. context.WithTimeout
  4. context.WithDeadline

Context with Value

  • WithValue accepts a parent context and returns a context copy. As a result, rather than overwriting the value, it creates a new duplicate with a new key-value pair.
  • Therefore, you should only use WithValue with data that is within a specific request scope.
  • Passing function arguments or values that will be modified later can result in the formation of many context variables, which will significantly increase your memory usage.

Syntax:

Example :

Full Code:

Output for the above code:

Run the Code!

Explanation of the code:

  • In the above code, we are assigning a new context to the variable ctx which holds the parent context, we got the value stored in the context from the main function which is also available in the myfunc method.
  • When you called context.WithValue, you passed in the parent context and obtained a context in return. Because of the context.WithValue function did not modify the context you provided, you received a context back. Rather, it enclosed your parent context within another with the new value.

Working Example:

Output for the above code:

Run the code!

Explanation of the above code:

  • The exampleContext function's goal is to see if the specified context contains a value whose key matches the provided key. If the value is present, we print it out; if not, we print a message to the terminal informing the user that the context value could not be located.
  • we create a variable whose value is of type keyType(keyType is the custom type created), in this example, the variable key is for clarity purposes but it can be named as it suits you, this variable will then be passed to the exampleContext function. The second argument the exampleContext function expects is a context of type Context.
  • When sending values, particularly those that are request-scoped, the withValue method is essential.
  • Making HTTP requests is one of the withValue function's most popular use cases. With the Context() method, the net/Http package in Go provides internal logic that builds a context for each request.

Determining If a Context is Done

The Context type offers a method called done() that can be used to determine whether or not a context has ended.

For Example:

Output for the above code:

Run the code!

Explanation of the code:

  • In this example, myfunc() is called 5 times before the time.Sleep(...) method completes, and the context.WithCancel(...) function delivers a value to the channel ctx.Done(), which terminates the for loop and exits.
  • As explained in the context package documentation, context.Background() is used as the foundation for establishing a new context variable.
  • This is a good way to mark the completion of a task performed in a goroutine, and it is particularly effective in this scenario.

So, if the channel returned by ctx. Done() is closed, the context is complete, and the execution should be aborted.

Context with Cancel

  • WithCancel method returns a copy of the parent context along with a cancel function; invoking the cancel function releases resources connected with the context and should be called as soon as operations in the Context type are finally completed,

Syntax:

For Example:

Output for the above code:

Run the code

Explanation of the above code:

  • In the above code, the purpose of the sayMyName() function is to print out your Name after the duration of time provided If it is not canceled by the context.
  • This happens because we canceled this function after two seconds while your name was supposed to print after six seconds.
  • This is very handy, When using servers and performing network requests, a user can elect to cancel their request before they opt to elect to cancel their request before to receive a response. This would be handled politely because canceling the context releases resources connected to it.

Context with Deadline

  • WithDeadline , is used to specify a completion date for the context and stops automatically after that date has passed.
  • Similar to setting a deadline for oneself, you can set a deadline for context. Go will automatically cancel the context for you if the time limit you specified for it to finish is reached.

In simple terms, A golang context deadline is an exact moment when the context is considered to be "done," and any work that is covered by the context that is completed after the deadline should be canceled.

Syntax:

For Example:

Output for the above code:

Run the code!

Explanation of the code:

  • In the above, we used Go Select to spin up two goroutines that will compare Context deadlines and time.After.
  • This happens because we set our context shortDuration for 1 millisecond and time.After 1 second which is more than the expected time duration.
  • If we increased the time of our shortduration more than 1 second it will result in overslept, because in that case, time.After would’ve finished counting.

Context with Timeout

  • WithTimeout, allows a program to continue where it might otherwise hang, giving the end user a better experience.
  • Timeouts/Deadlines can be used to protect against corrupt or failing dependencies. It accepts a brief period as a parameter, along with the parent context, and terminates the function if it runs beyond the timeout period.

The timeout context defined in the following example will terminate after 4 seconds.

The WithTimeout function in this case accepts a parent context and a duration parameter, and it returns a child context with a deadline set +to the fixed duration.

For Example:

Output for the above code:

Run the code!

Explanation of the code:

  • In the above code we started a goroutine that we want to halt if it goes longer than the 2-second timeout interval we've set. We then postponed the call to cancel() and started the goroutine that will time out if our deadline is surpassed.
  • You'll see a for loop within the myfunc() function that represents a long-running operation. We are always checking to see if the Done channel within the parent context object has been closed due to timeout, and if it hasn't, we continue to print doing something amazing every half-second until the timeout is reached.

Important Points

  • context.Background should only be used at the most detailed level as the source of all derived contexts.
  • context.TODO should be used when you're not sure what to use or if the current function will be modified to utilize context in the future.
  • context.Value is not recommended to use with context. It is rarely used in the API as the request-scoped. Instead of considering it s an optional parameter, all critical values should be passed as function arguments.
  • Context cancellations are recommended; it may take some time for the functions to finish up and quit.
  • Functions should preferably take contexts as the first argument instead of storing them in a struct.
  • The Context struct lacks a cancel method because only the function that derives the context should cancel it.

Conclusion

  • When it comes to program design, the Golang context package is an extremely useful tool. The application of context.
  • TODO() and the context Background() are used to create empty contexts and are made more powerful by wrapping them with WithTimeouts, WithDeadline, or ~ to control the program's flow.
  • The Done method is used to identify when a context has finished executing and context value to add values to new contexts and to retrieve them in other functions using the Value method.
  • Always use ctx, if you use a different variable name, just follow the majority; you don't need to be unique with stuff like this.
  • Ensure that the cancel function is used.
  • Never use a struct to add a context to a method; instead, add it to the parameter, i.e context.

Thank you for your time. I hope you found this information useful.