Golang Context in Action

 • 

Intro

Context is available in Go for a long time. But before moving to Go’s standard library, we should access context from /x/net/context package. In my opinion, context is one of the most important package in Go that every Go developer should use. By using context we can do various things, like saving value in context using ctx := context.WithValue(parentContext, key, value), doing something and set timeout ctx, cancel := context.WithTimeout(parentContext, time.Second*5), etc. I found context is also useful to create a timeout middleware for web application in Go:

func timeout(h CustomHandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		respChannel := make(chan Response)

		ctx, cancel := context.WithTimeout(r.Context(), time.Second*time.Duration(rtr.opt.Timeout.Timeout))
		defer cancel()

		r = r.WithContext(ctx)
		go func() {
			handlerResp, err := h(w, r)
			if err != nil {
				// do something with error
			}
			respChannel <- handlerResp
		}()

		select {
			// do something when context done happened
			case <-ctx.Done():
				w.WriteHeader(http.StatusRequestTimeout)
				w.Write([]byte(Request time out))
				Return
			case resp := <- respChannel:
				// do something with response
		}
	}
}

The timeout middleware will tell the endpoint to not taking more time than the deadline. If it’s taking more time than the timeout, it will automatically response with 504(Request Time Out) . And because context is passed to http.Request, we can use or pass the context to our function using http.Request.Context().

HTTP Client

In go 1.7 you can use context.WithTimeout for http.Client.Do(req):

req, err := http.NewRequest(GET, someurlhere, nil)
if err != nil {
	// do something with error
}

ctx, cancel := context.WithTimeout(r.Context(), time.Second*3)
defer cancel() // don’t forget to cancel the context, otherwise it will leaking

req = req.WithContext(ctx)
resp, err := http.DefaultClient.Do(req)

By using this code, you will get context deadline timeout exceeded error if your http request time exceed the context timeout. This way, you can also handle the timeout per endpoint and not relying on http.Client.Timeout.

Database

What about database process, can we kill database process that take a long time? In Go 1.8, database/sql package add support for context. Now we can cancel long running queries from our golang client. This code sample below is using github.com/tokopedia/sqlt as database library/wrapper.

db, err := sqlt.Open(dsnmaster, dsnslave)
if err != nil {
	// do something with error
}
 
var result []int
query := SELECT id, number FROM something WHERE id = ?”

// cancel query that is running more than 2 secs
ctx, cancel := context.WithTimeout(context.TODO(), time.Second*2)
defer cancel()

err = db.SelectContext(ctx, query, &result, 10)
if err != nil {
	// do something with error
}

Context deadline error will happened if query in the code run more than 2 seconds, and database driver will cut/close the database connection for that query.

Tracing

The use case for context is very vast, even you can trace your go program using opentracing.io with the support of context. To be able to trace your program, context need to be passed to function and use opentracing library. Learn more about tracing your Go program with opentracing.io, it will be very helpful.

Learn More

Now we know that context is very useful and used widely in Go. But that is not all, to learn more about context you can go to this godoc link https://godoc.org/context. Or you can watch this very helpful video about context by Francesc: https://www.youtube.com/watch?v=LSzR0VEraWw.

Hopefully you enjoy this blog post, and don’t forget to add context.WithValue to your life :)