Context in go 101

Pre-Requisite

Why Context?

  • A calls B
  • 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

Waiting for a Slow Server
  • A Sample slow server is started . The handler sleeps for 10 second before responding to any request.
  • Client makes a request and then waits for 10 seconds before it gets the response from Server

A Timeout handling Pattern with Context

Step 1

//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)
  • Context is a Tree i.e a Parent with many descendants
  • Background is the root of any Context tree (Represented by the code context.Background()) .
  • context.WithTimeout(context.Background(), timeout) creates a copy of context with a deadline set to current time + specified timeout

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

  • Recall that our objective is to process the response if it happens before the specified timeout, else give up on the request
select {
case <-ctx.Done():
< -fResult //let the go routine end too
return ctx.Err()
case err:= < -fResult:
return err //any other errors in response
}
  • When we create a derived Context, in this case with timeout, the returned context has a Done channel. Done channel is closed when the deadline expires and hence works as a perfect broadcast mechanism
  1. The deadline expires (Done channel is closed)
  1. The GET is successful and the ResponseHandler processes the response. Any errors in processing are returned in the error channel.

Example Run

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

Context is Propagated

  • The line ctx := r.Context() retrieves the context associated with the request
  • 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

--

--

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