Context in go 101

Pre-Requisite

Article assumes that Reader is familiar with Goroutines, channels and HTTP

Why Context?

The primary idea behind context is the ability to cancel unneeded work.

  • B starts to do work
  • While B is working, A no longer needs the result either because B is slow, or A’s caller no longer needs the result (say because of a user action or expiry of some deadline)

HTTP GET call to a Slow Server

The picture below depicts a simple REST client calling a GET on HTTP Server that takes time to respond.

Waiting for a Slow Server

A Timeout handling Pattern with Context

For the impatient, here is the full code. In the sections below we will go through it step by step

Step 1

For better code organization we define a type called ResponseHandler . This is a function that can process the result of http request. Implementation of this handler can hence contain any custom logic as required

//Func Type to handle response
type ResponseHandler func(resp *http.Response, err error) error
//Implementation of Response Handler
func stdOutHandler(resp * http.Response, err error) error {
if err != nil {
return err
}
defer resp.Body.Close()
//Handle the response
//In this case we just print the body
body, _: = ioutil.ReadAll(resp.Body)
fmt.Printf("Body from response %s\n", string(body))
return nil
}

Step 2

  • Create Request with a Timeout context. This is the key
ctx, cancel := context.WithTimeout(context.Background(), timeout)request, _ := http.NewRequestWithContext(ctx, "GET", getURL, nil)
  • Background is the root of any Context tree (Represented by the code context.Background()) .

Step 3

  • Do the request with context in a Goroutine.
fResult: = make(chan error, 0)
go func() {
fResult < -respHandler(http.DefaultClient.Do(request))
}()

Step 4

*️⃣ The Select

select {
case <-ctx.Done():
< -fResult //let the go routine end too
return ctx.Err()
case err:= < -fResult:
return err //any other errors in response
}

Example Run

Running the gist, would report the following error (your port may differ since the httptest server picks a random port)

Error in GET request to server http://127.0.0.1:60171, error = context deadline exceeded

Context is Propagated

We saw that handling timeout on the HTTP Client allowed the caller to walk way from a slow response, but what about the Server? Wouldn’t it be nice if the Server could also know that the Client no longer cares about the result and stops processing it.

  • We now use the same methodology as used in case of request — start a Goroutine which does the job (in this case just sleeps for 12 seconds and then writes “Hello There” ).
  • After the Goroutine is spawned, the handler starts to wait on either a message on Done channel or from the channel fed by Goroutine
  • In our current case, since the timeout on the request is 6 second and the job takes 12 seconds, what we would see is the following error on the server side — Error context canceled when the context is canceled by the client.
Context Propagation

Next

This is just the tip of iceberg but hopefully provides enough “context” to try more complicated scenarios of Context usage.

--

--

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