Go Concurrency Patterns: Context
blog.golang.org
blog.golang.org
> At Google, we require that Go programmers pass a Context parameter as the first argument to every function on the call path between incoming and outgoing requests.
This pattern demonstrates a fundamental difference between Go and languages that encourage thread-state data. The arguments for thread-state include not requiring all functions to pass such a context variable, since it is (hopefully) already populated in the thread state. The hopefully part is where thread-state becomes unpleasant, because if you want to do concurrent work, you must pass one thread's state to the other. The other thread has no guarantee that it is there, so the programmer must either hope it is or verify it, which is annoying.
The context-passing style doesn't require that kind of hope, but does require each function to pass the context to another. When I was working on a medium-sized go project, I would often need a context deep in the stack, and be forced to add it to many functions up the tree. Hence, having the standard that all functions get the context always seems excellent because it is no longer something to think or worry about and doesn't increase code length by an unreasonable amount.
I heartily agree with this recommendation and will be using it going forward in my go projects.
how about new paradigm - context-oriented programming :) where, like "this/self" in OOP, ctx is always present. And looking through the posted article Google is somewhat there with context [instance level] kind of inheritance (the context tree)
This is how OOP in C is done though usually its done with the context being an opaque object.
Having functions with multiple arguments that depends on other functions with multiple arguments that depends on yet other functions with multiple arguments will result is very hard to manage function dependencies sooner or later and "context-oriented-programming" solves this problem very nicely.
A function that takes a single argument as a pointer to a struct can be extended indefinitely without breaking the function API.A function that takes its arguments by individual arguments will break everytime it need to be extended by an additional argument.
Right, so then you are in a situation where you can always rely on it implicitly, until you can't.
Much better to be consistent and force the inclusion everywhere as you mention in your second paragraph.
1) add an explicit Context parameter to each function that needs one; typically this is the first parameter and is named "ctx". This makes it obvious how to cancel that function and pass stuff through it, but it's a lot of work. We are developing static analysis and refactoring tools to help automate tasks like this.
2) use a package to map http.Requests to Contexts. This requires that whatever server handler you're using register a Context in a map for each http.Request and remove it when the request completes. You could do this using the gorilla context package, for example.
My personal preference is the explicit ctx parameter, since then libraries are agnostic to the framework being used. Different frameworks can provide their own Context implementations; middleware should not care.
// h already has a context.Context in scope through similar
func MyHandler (ctx context.Context, h http.Handler) http.Handler {
return http.HandlerFunc(func (rw http.ResponseWriter, req *http.Request) {
// do stuff before handler
h.serveHttp(rw, req)
// do stuff after handler
})
}
then you can just chain your handlers and pass through a shared contextI did think it seemed nice as a pattern, but not as a library... then I got to the idea of a black box for context values. That really concerns me. If a function and a function called by that function both depend on some sort of parameter, why not just specify it as an argument like everything else? If it were somehow possible to only have request handling related functions for a specific application require a context value, that would make sense, but requiring every function called in the process of handling a request to have a ctx value as its first parameter? That seems dangerously infectious, and like using a rake to scratch an itch.
I'll have to wait and see how libraries implement this in practice, but I hope most libraries will continue to just follow the simple, elegant route - if a function may block, document it, and let the caller handle putting it in a goroutine, instead of creating yet more boilerplate.
This is why dependency injection frameworks like Guice and Dagger went with a different approach: they provide environment values to a class's constructor using reflection. Then the framework can do type checking at startup (for Guice) or compile time (for Dagger).
However, that has different costs: it requires a class per component instead of a function per component, and the framework is more complicated. This is an engineering tradeoff and apparently the developers of Context decided on simplicity.
However, even on moderately complex Go projects, I have not yet found a need for dependency injection. It's major value in Java was testing, which is usually done another way in Go. (Fewer mocks, fewer fakes, more access to private package parts in tests.)
Context propagation on the other hand, has proven valuable. We get cancellation and timeouts that work reliably across RPC boundaries.
I'm not sure why none of the frameworks seem to be pursuing this, so I'm interested in why this doesn't work. (And yes, it's not completely as slick as a dynamic language... I'm comfortable with it taking a bit of manual, explicit work, since none of the other options are completely automatically working with no negative tradeoffs either.)
Go definitely doesn't have the machinery in place to dynamically create an interface for C that might compose A or might compose B, such as Haskell might do, but it seems like it could still work to me.