One of the shiny new toys in go 1.7 is the ‘context’ library. Not
shiny as in it is genuinely new (I still tend to import it from
golang.org/x/net/context), but given this library has been
considered significant enough to make it into the standard library, it
must have at least a glimmer in the right light.
In the following posts, I’ll show how I’ve found it can be used for good effect for several common problems facing any programmer of services:
- making sure you can make sense of your application logs
- keeping on top of performance using application profiling
- figuring out what went wrong, in the event of a panic or unhandled exception
I’ll also include a post about some other applications of the module I’m excited to be working on soon, and some slippery slopes or anti-patterns I’ve discovered.
By ‘service’ do you mean API service or what?
It doesn’t matter an awful lot. It could be a regular HTTP service of some kind, a consumer of kafka topics, a cog in a vast stream processing machine, anything - the key characteristic being that a single go program is responsible for processing more than one independent thing at a time, or at some point you would want to switch it from processing one thing at a time to many things at the same time. Why else are you using go?
Enough future tense. Give me some content. What does
context do ?
context enables a bunch of cool libraries to get access to stuff you
know you shouldn’t have to muck with every single time for simple,
every day jobs. Let me explain…
One of the things you’ll notice after not very long at all using go is that it doesn’t really use global variables very much. This seems all well and good and clean when you’re writing your own code to implement logic, but quickly becomes problematic when it comes to calling or writing library code.
Some very basic things that you didn’t realize used a global (or thread local state) in your old favorite language, all of a sudden now must be passed around as formal stack arguments. You can use globals, but if you don’t protect their access with a mutex, they’re not safe. That works for truly global state, but then you realize that because you have only one process with a bunch of goroutines instead of one process per request if you’re from python, or one thread per request if you’re from ruby or java, that globals just won’t work. There is just nowhere to store this kind of per-request or per-processing unit information except the current function scope.
You could just keep adding extra function arguments. This is fine and proper for most things, but when you’re talking about concerns like logging or profiling, it just seems a bit ugly to pollute your function arguments with a log object, a database transaction handle, a profiling object, and whatever else.
You need another plan for this. That plan is
Instead of passing around a bunch of random objects for these concerns all over your code base, you can pass around just one, and those libraries can get things out of them by themselves when they need them.
Globals, huh? Anything else?
Yes. It fixes the “stop” and “reload” buttons for people using your site. Again, in more detail…
A problem that HTTP services have to deal with is stopping work when connections are closed by the other end. If you don’t, your handler method is still running and might not notice that anything is awry until it tries to write the response. For anything which might do a database update, this is bad. Instead, you probably want to know commit an open transaction that the user hasn’t clicked the “stop” button. If it’s running an expensive query, and your user keeps hitting “reload” because the page didn’t load, you might now have an extra query running for every time that impatient user retried.
Similarly, if you’re writing code which does a lot of heavy computational work, and you’re not sure if it will complete in a reasonable time, you might want to have some kind of escape hatch so that it can stop if it’s taking too long.
As with globals, the old tricks used in interpreted and excessive threaded languages don’t work. You can’t just cancel a thread, kill a forked process or set an alarm. Canceling threads was never a good idea anyway; it wouldn’t kill any sub-threads those threads made, for example. Pretty much every thing which does work has to check for these termination signals, and they all need to do it the same way. That’s fine if it’s a system function (as with threads or processes), or your language uses a virtual machine or an interpreter or something slow like that. The virtual machine can stand in for the system. But in a language like go, again this must be done in your code.
And if you change the policy on how to stop the code processing, you
sure don’t want to have to change your code all over again.
solves this problem with its
This falls into the general bucket of quasi-global concerns, and while
in theory you could just use some library which stored the things it
context, it works better that this is standard.
That sounds great! Where’s the code?!
Coming soon in the next post!