Implementing Timeout in Golang

Kevin Wan
Kevin Wan
Jun 1 · 6 min read
Photo by Icons8 Team on Unsplash

Problems

  • If the response time is too long, the users may have left this page, but the server is still consuming resources to process, and the result obtained is meaningless.
  • Too long server-side processing will cost too much resources, resulting in a decline in concurrency, and even service unavailability

Why Go timeout control is necessary?

Go is normally used to write backend services. Generally, a request is completed by multiple serial or parallel subtasks. Each subtask may issue another internal request. When the request times out, it’s better to return quickly and release the occupied resources, such as goroutines, file descriptors, etc.

Common timeout control on the server side

  • In-process processing
  • Serving client requests, such as HTTP or RPC requests
  • Calling other services, including calling RPC or accessing DB, etc.

What if there is no timeout control?

In order to simplify, we take a request function as an example. It does not matter what it is used for. As the name suggests, it may be slow to process.

When we use this kind of code to serve, the familiar image shows up for one minute. I guess most people can’t wait that long, but the server is still working on that even the page is closed. And the processing resources are not released to serve other requests.

This article will not go deep into other details, only focus on the timeout implementation.

Let’s take a look at how timeout work and what kind of pitfalls shall we take care.

Version 1

Before read further, think about how to implement the timeout of the function.

Here is our first try:

Let’s write a main function to test it.

Run it to we’ll see.

The timeout has taken effect. But is it all done?

goroutine leak

Let’s add a line of code at the end of the main function to see how many goroutines are there.

Sleep 2 minutes is to wait for all tasks to be done, then we print the current number of goroutines. Let’s run it and see the result.

Oops, the goroutine leaked, let’s see why this happens? First, the function exits after a timeout of 2 seconds. When the function exits, the isn't being received by any goroutine. When the code is executed, it will always be stuck and cannot write. This kind of problem causes each timeout request to occupy a goroutine forever. This is a seious problem. Each goroutine takes 2-4K bytes memory, and when the memory is exhausted, the process exits unexpectedly.

So how to fix it? In fact, it is very simple, the only thing that we need to do is to set the to 1 when , as below:

In this way, can be written regardless of whether it timed out or not, without getting stuck in the goroutine. With this method, someone may ask if there will be a problem if you write to a channel that is not being received by any goroutine. In Go, the channel is not like the resources like file descriptor. It does not have to be closed, it is just an object, is only used to tell the receivers that there is nothing to write, no other purpose.

After changing one line of code, let’s test it again.

The goroutine leaking problem is gone. Awesome!

panic cannot be captured

Let’s change the code of the function to

Modify the function to catch the exceptions as below:

If you execute the code, you will find that panic cannot be captured. The reason is that other goroutines cannot capture panic generated in the goroutine from within .

The solution is to add to the . Similarly, the buffer size of needs to be 1, as below:

With this code, panics can be handled outside of .

Is the timeout period correct?

The above implementation of ignores the incoming parameter. If has a timeout setting, we must pay attention to whether the incoming timeout is less than 2 seconds we given here. If it is, we need to use the given timeout setting in argument. Fortunately, compares the timeout and sets to the less one, so we just modify the code like below:

Data race

In our example, just returns an parameter. If you need to return multiple parameters, we need to pay attention to , which can be solved by . For specific implementation, please refer to go-zero/zrpc/internal/serverinterceptors/timeoutinterceptor.go, I won’t go deep into the details here.

Complete example

More timeout examples

CodeX

Everything connected with Tech & Code

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store