Managing channel operations using context
I was recently working on a small personal project when I came across a problem during some testing. I had written an application that starts a HTTP server, accepts requests, and serves content from an in-memory data store.
The basic application logic is as follows:
- Start servers and listen for requests.
- Create a channel to handle data store read requests.
- Upon accepting a request, push a data store read request onto the channel.
- Send data from data store down returning data read request channel.
- Serve data to client.
So, the problem I noticed was the ability for the request to be cancelled at any point for one of several reasons: request timeout; client cancellation; server error; etc. Any one of these errors can happen at any point throughout the request, more importantly, once the individual request has been pushed onto the data read request channel. This is a huge problem as there could be potential items in this channel that are no longer needed, and because unbuffered channels in Go are blocking, the sender blocks until the receiver has received a value.
After a bit of trial and error, research, and unanswered questions, I decided to use the request context to try and solve this problem. The context package was introduced into the standard library in Go 1.7, but prior to this it was part of the golang.org/x/net/context library. To give you a bit of
At Google, we developed a context package that makes it easy to pass request-scoped values, cancelation signals, and deadlines across API boundaries to all the goroutines involved in handling a request.
As mentioned earlier, the request can be cancelled at any time, therefore we have to check for this in several different places. Initially, I did this in the request handler, but after some more testing I realised the request could have made its way to the read channel. In that case, we also have to check for any cancellation while getting data from the data store.
I am still unsure if this is the best approach, but I have manually and unit tested several scenarios and I am pretty confident with this solution. Some basic pseudo code is as follows:
Feel free to get in touch if there is a better solution to this — I’d be more than happy to hear other people’s opinions.