Golang Function Timeout With Context

Timeouts can be important for an application. It can limit how long is the maximum duration of a process. We can save resources by cancel further processes when timeout happened. We can use context to apply a timeout to a function call in Go.

To use context timeout for a function call we need to do these steps:

  • create a context with timeout
  • create a channel to flag if the function is completed
  • call the function in a goroutine
  • send a flag to the channel after the function is completed
  • wait which one happened first, context timeout or the function complete

One of the common usages of it is in middleware. It put a time limit for the handler process duration and will give an error response if timeout. So the client won’t wait for too long to get a response.

Context timeout in a middleware

The code below is a middlware to give timeout to handler process.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func processTimeout(h http.HandlerFunc, duration time.Duration) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        ctx, cancel := context.WithTimeout(r.Context(), duration)
        defer cancel()

        r = r.WithContext(ctx)

        processDone := make(chan bool)
        go func() {
            h(w, r)
            processDone <- true
        }()

        select {
        case <-ctx.Done():
            w.Write([]byte(`{"error": "process timeout"}`))
        case <-processDone:
        }
    }
}
To use the middleware
http.HandleFunc("/get_cities", processTimeout(myHttpHandler, 5*time.Second))

On line 3 we create a new context with a timeout duration and then create a new http.Request with the context on line 6. This http.Request is the one that will be used in the handler. On line 8 we create a channel to flag if the handler process is complete.

We call the handler in a goroutine function and send a flag to the channel after the process is done. Because the handler execution is in a goroutine, the execution will continue without waiting for the handler to finish. Then we block it with a select statement to wait for which event happened first, context timeout, or handler process done. If context timeout before handler is done, we will give an error response to the client.

The context timeout not only can be handled on the middleware but also inside the handler. You can detect if the context is done and stop the execution. One example of the use-case is to use context from http.Request on sql query execution. With the context, we can stop the process because we already give an error response to the client. So continuing the process after context timeout is pointless. Unless in some case where you want to continue the process without the client know the result immediately.

Conclusion

Golang context has timeout that we can use for a time limit for a function call. We can use it in middleware to limit the duration of the handler process. The handler can also use the timeout to stop continuing the process. By doing this, we can save our precious resources to handle other processes.


See also