[Blog] Context should go away for Go 2

1,275 views
Skip to first unread message

faifa...@gmail.com

unread,
Aug 7, 2017, 9:40:05 AM8/7/17
to golang-nuts
Hi everyone!

I've written a blog post called 'Context should go away for Go 2':

https://faiface.github.io/post/context-should-go-away-go2/

The post is about the cancelation problem in Go, how context package solves it, why context package is bad anyway, and that Go 2 should do something about it. Hope you enjoy reading ;)

PS: I'm not sure if this post is acceptable for an experience report. If you think it is / isn't, let me know.

Michal Štrba

as....@gmail.com

unread,
Aug 7, 2017, 11:20:34 AM8/7/17
to golang-nuts
I also dont want to see context everywhere. At the same time, I don't want it to be obfuscated in a confusing way.

ctx is an unfortunate initialism and context.Context package stuttering doesn't help.

I suppose you could stuff a pointer on the stack pointing to a potential context and then provide a built-in to access this structure. But that already sounds confusing and easy to ignore, and you've added a few bytes of overhead to each function call by providing an auxillary argument to every function and method.

Linker

unread,
Aug 7, 2017, 11:33:13 AM8/7/17
to as....@gmail.com, golang-nuts


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Regards,
Linker Lin

linker...@gmail.com

Daniel Theophanes

unread,
Aug 7, 2017, 12:06:23 PM8/7/17
to golang-nuts
Hello Micha,

I'm not seeing any experiences (project building/maintaining) you are reporting on.

In the projects I've worked on the switch to Context has been a huge win all across the board. I think it would be sweet to find an effect way to make io.Reader take a context (I often want just that, and when I really need it the solution tends to be more inefficient then it could be).

Note, Context isn't just useful for servers. It is useful for any application. Desktop applications with GUIs, services/daemons, web services. In other words, if your application does an operation that could block (file IO, network IO, running executable) and you either have a halting program or a program you want to be responsive, Context is exceptionally useful.

As for Context.Values, I think you are dismissive of the real use-cases without giving proper weight to them. It can be abused. Just like go-routines and channels. I'm not saying we can't do better then the current implementation, we probably can if the benefit is seen as worth the cost of the change.

You're saying context is viral. It is. Just like errors are viral. Once you start returning them you need to propagate them back. Having a context argument is saying "this function, or something this function calls may block for undisclosed amounts of time and we need to know when to stop blocking and back out." There might be a better way to do ctx (and maybe errors) if the language was adjusted; I'm fairly sure go-routine local storage isn't the answer but maybe something else would be. Maybe you could create an Arena with a context and a pattern for returning execution on error and an Arena could use multiple go-routines and such. However I think where you would need to start is a named real experience building and maintaining a solution in Go that gets real use. Then look at it and see where it was painful and how it was so.

Lastly as an aside, github emoji reactions are fine for what they are. But they are not an engineering argument one way or another. Last time I checked no one is running a popularity contest.

Thanks, -Daniel

Uli Kunitz

unread,
Aug 7, 2017, 12:33:03 PM8/7/17
to golang-nuts
I assume here that the proposal is not to change io.Reader but to create a new interface, maybe context.Reader. There are lot of uses of io.Reader where the Read operations will not block and therefore no context is necessary. A context.Reader would also require a wrapper function that converts a context.Reader to an io.Reader.

Linker

unread,
Aug 7, 2017, 10:16:29 PM8/7/17
to Daniel Theophanes, golang-nuts
Hi, Daniel!
    Do you know Goroutine local storage ?
How about replace Context with the Goroutine local storage ?

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

as....@gmail.com

unread,
Aug 8, 2017, 1:46:53 AM8/8/17
to golang-nuts
Threads are the structural and functional loci of execution on some operating systems, data stored in a box accessible to each thread makes sense when you obey the constraint that the things in that box are to be used for that thread exclusively.

Goroutines are different, they function like threads but have different structure. Structurally, they are not intended to preserve state accessible to the user. Goroutines are light, and I suspect that the idiom of goroutine local storage is incompatible to the design of the language: anonymous loci of flow control communicating through channels as conduits.

If I could wish for an idiom taken from operating systems, TLS/GLS wouldn't be my first choice. I see namespaces serving a similar role on Plan9. The ability to share a namespace with the child or parent process. Namespaces include the environment and allow the caller too configure what resources a process has access to upon execution. Such an addition would also violate the unwritten rule of anonymous processes, but context is already doing that. The difference is that context is based on what is being executed (what function is called) and not where (which goroutine). Contexts are like function namespaces. They even have a map of arbitrary key-values.

The exercise is to find a way to make this feel natural to the language, and apply such an approach to functions instead of processes, threads, or goroutines.

Daniel Theophanes

unread,
Aug 8, 2017, 10:20:40 AM8/8/17
to as....@gmail.com, golang-nuts
I'm personally reasonably happy with Context as-is. Bit if you are looking for a better solution, I completly agree with "as.utf8": the solution won't be found in TLS/GLS (goroutine local storage) but in some other contextual/namespace/arena type thing that is larger then a single goroutine, is still cooperative, and can expose properties that allow transporting it over the network as Context allows today.

--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/eEDlXAVW9vU/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.

Piero de Salvia

unread,
Aug 8, 2017, 12:20:08 PM8/8/17
to golang-nuts
I agree with Dave Cheney and Michal Strba that Context should not be used for storage of any kind and only serve the purpose of cancelation.

In effect, the original purpose of Context, I think, was avoiding cascading failure in chains of  *network* calls, where the result of one call would be used as argument to the next call. In effect, a kill switch on the whole chain.

A deeper analysis of this feature makes me think that this is not so much about goroutines (hence not so much about thread local storage) as much as network failures.

And finally, I think the last thing anybody wants is to pollute the whole stdlib with Contexts.

To me, given these requirements, context should become embedded in all network functions, just like a timeout is.

Only difference, when creating a network function (client or server) one would (optionally) call SetContextName("MyContext") on it (I think the namespace suggestion from plan 9 is excellent), so a chain of network calls would belong to the same named context.

So when code owning that chain of network calls would need to cancel (in effect kill) the chain, it would just call a lib function called Cancel(ctx string) and the whole chain of calls would be killed: every network functions must obey a Cancel() call.

As far as extending it to Reader, it does not make sense to me. The reason why there is a Context today is that failures in chained network calls are difficult to detect and correct, because of inherent network characteristics. That is not a general case for a Reader.

Sam Whited

unread,
Aug 8, 2017, 5:35:18 PM8/8/17
to golan...@googlegroups.com
On Mon, Aug 7, 2017, at 11:33, Uli Kunitz wrote:
> I assume here that the proposal is not to change io.Reader but to …

There is no proposal. This was briefly discussed in the contributors
summit, but the underlying issues are not necessarily well understood.
Blog posts like this one help us all understand those issues better.
Apologies if I made it sound like this was something that was going to
happen in the contributors summit post; everything at the summit was
just a discussion which, for the most part, avoided concrete solutions.

—Sam

Lucio

unread,
Aug 10, 2017, 12:34:34 AM8/10/17
to golang-nuts
If you treat a timeout as a kind of failure, then context makes more sense. In fact, context deals with unblocking a procedure when this is found necessary. That's really all there is to it. Why it is blocked, when it needs to be unblocked, just confuse the picture. The context narrows the "damage" required to unblock something to functions that have been instructed how to deal with it.

In the full picture, the "error" argument to a function necessarily becomes part of the context, not of the argument list (return values are just a special case of arguments, just like the receiver is). So is a timeout, of course, and distinguishing them is an option in some cases, a necessity in others, but from a "context" perspective it is what the Univac Exec-8 programmers rightly called a "contingency".

The name "context" could not have been more fortunate, as the context is very much the gift-wrap that keeps related activities linked and determines what happens when the defined conditions (and perhaps some unexpected ones) are encountered.

That, at least, is my uneducated understanding of where the "context" concept is heading. I'm looking forward to a much more robust programming paradigm when the final outcome is eventually reached.

Lucio.

Sam Vilain

unread,
Aug 11, 2017, 6:59:05 PM8/11/17
to golang-nuts
On Monday, 7 August 2017 09:06:23 UTC-7, Daniel Theophanes wrote:
In the projects I've worked on the switch to Context has been a huge win all across the board. I think it would be sweet to find an effect way to make io.Reader take a context (I often want just that, and when I really need it the solution tends to be more inefficient then it could be).

Right; the blocking-style interface of io.Reader is a problem here; I didn't really consider this, but my proposal (on its own thread) solves this problem; readers that know how can read from the sync.Done channel (which would have to be a 'close on cancel' type channel to allow multiple readers).
 
As for Context.Values, I think you are dismissive of the real use-cases without giving proper weight to them. It can be abused. Just like go-routines and channels. I'm not saying we can't do better then the current implementation, we probably can if the benefit is seen as worth the cost of the change.

+1.  I used the values interface first and found it much better than adding extra parameters to every function, or passing around large interface types with a bunch of getter methods.

You're saying context is viral. It is. Just like errors are viral. Once you start returning them you need to propagate them back. Having a context argument is saying "this function, or something this function calls may block for undisclosed amounts of time and we need to know when to stop blocking and back out."

Or "this function wants to log or profile some inner function" or "this function wants to use cached objects read within the same transaction".

In my experience, in real server applications you always want these abilities, but few structure their code to include context arguments. The status quo is that logging is almost always really bad in Go server applications.  Caching values read within a transaction is too hard so you'll make more DB calls than necessary.  Profiling never covers the tightest loops in your code.  This should be easy to add to an existing application without massive refactoring.
 
There might be a better way to do ctx (and maybe errors) if the language was adjusted; I'm fairly sure go-routine local storage isn't the answer but maybe something else would be. Maybe you could create an Arena with a context and a pattern for returning execution on error and an Arena could use multiple go-routines and such. However I think where you would need to start is a named real experience building and maintaining a solution in Go that gets real use. Then look at it and see where it was painful and how it was so.

"Goroutine Local Storage" is bad in my opinion because it's effectively a global, even if it is private to the goroutine.  I believe context.Context is the right pattern, in that values are rolled back as the call stack unwinds, and building it inside the runtime allows performance optimizations (variable elimination, slot unrolling, combination of context values into per-scope arenas) which would otherwise be impractical or ugly.

Sam

Axel Wagner

unread,
Aug 14, 2017, 2:08:56 AM8/14/17
to golang-nuts
For the general interest: I wrote up my thoughts on this in a lengthy blog post:
Why context.Value matters and how to improve it
In particular, I sketch a design how context.Value could be made a language-level feature removing most of the disadvantages often ascribed to it. With this I am trying to put a more concrete face to the ideas behind the suggestion of using goroutine-local storage or the like to solve the issues. While I can't speak for others, I see similar ideas hinted at in this thread.

I hope this can help to steer the discussion into the more helpful underlying questions, instead of getting hung up on details of the current implementation.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.

roger peppe

unread,
Aug 14, 2017, 6:11:12 AM8/14/17
to Axel Wagner, golang-nuts
On 14 August 2017 at 07:08, 'Axel Wagner' via golang-nuts
<golan...@googlegroups.com> wrote:
> For the general interest: I wrote up my thoughts on this in a lengthy blog
> post:
> Why context.Value matters and how to improve it
> In particular, I sketch a design how context.Value could be made a
> language-level feature removing most of the disadvantages often ascribed to
> it. With this I am trying to put a more concrete face to the ideas behind
> the suggestion of using goroutine-local storage or the like to solve the
> issues. While I can't speak for others, I see similar ideas hinted at in
> this thread.
>
> I hope this can help to steer the discussion into the more helpful
> underlying questions, instead of getting hung up on details of the current
> implementation.

Thanks for the blog post. ISTM that you're essentially proposing
package-scoped thread-local variables. I don't think that's is a good
fit for Go. Specifically, it doesn't work when channels are used to
transfer control flow - for example by passing a function through a
channel to an existing goroutine that calls the function.

There are also possible issues the when a goroutine is started in the
context of a request, but actually is nothing to do with that request.
For example a long-scoped object might lazily create a service
goroutine the first time that it's used - with this proposal, its
dynamic scope would retain the context of the first request that
happened to trigger its creation.

The context.Context type might be a little unwieldy, but I believe
that its explicitness
is a good thing.
>> email to golang-nuts...@googlegroups.com.
>> For more options, visit https://groups.google.com/d/optout.
>
>
> --
> You received this message because you are subscribed to the Google Groups
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to golang-nuts...@googlegroups.com.

Axel Wagner

unread,
Aug 14, 2017, 6:38:29 AM8/14/17
to roger peppe, golang-nuts
On Mon, Aug 14, 2017 at 12:10 PM, roger peppe <rogp...@gmail.com> wrote:
Thanks for the blog post. ISTM that you're essentially proposing
package-scoped thread-local variables.

To be clear: I am not proposing anything, really :) I'm just trying to separate the different concerns people have about context.Value into inherent (to the problem) and accidental (due to the implementation) ones.

Your E-Mail is a great example of the kind of thinking I wanted to encourage :)
 
I don't think that's is a good
fit for Go. Specifically, it doesn't work when channels are used to
transfer control flow - for example by passing a function through a
channel to an existing goroutine that calls the function.

I haven't thought about that. However, while this would be an interesting (and potentially confusing) interplay between dynamic and lexical scoping (employed by closures), I don't think it "doesn't work". A dynamically scoped variable is bound to the stack it's executing on. It's conceptually equivalent to sending a func(context.Context) over a channel and calling it with the context.Context of the receiver. It works just fine.

Nevertheless. An interesting point indeed.

There are also possible issues the when a goroutine is started in the
context of a request, but actually is nothing to do with that request.

Again, haven't thought of that, interesting point. That would certainly require some working-around if required. There probably would be one of two ways to handle this: Either by not doing it, or by having a kind of "background manager", that forks of a goroutine early on and gets sent background tasks over a channel.

The context.Context type might be a little unwieldy, but I believe
that its explicitness is a good thing.

I agree, that explicit passing also has advantages. If a concrete evolution of context is ever actually on the table, that tradeoff certainly needs consideration.

Thanks for pointing out these issues :)


>> For more options, visit https://groups.google.com/d/optout.
>
>
> --
> You received this message because you are subscribed to the Google Groups
> "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an

roger peppe

unread,
Aug 14, 2017, 7:53:40 AM8/14/17
to Axel Wagner, golang-nuts
On 14 August 2017 at 11:37, Axel Wagner <axel.wa...@googlemail.com> wrote:
> On Mon, Aug 14, 2017 at 12:10 PM, roger peppe <rogp...@gmail.com> wrote:
>>
>> Thanks for the blog post. ISTM that you're essentially proposing
>> package-scoped thread-local variables.
>
>
> To be clear: I am not proposing anything, really :) I'm just trying to
> separate the different concerns people have about context.Value into
> inherent (to the problem) and accidental (due to the implementation) ones.
>
> Your E-Mail is a great example of the kind of thinking I wanted to encourage
> :)
>
>>
>> I don't think that's is a good
>> fit for Go. Specifically, it doesn't work when channels are used to
>> transfer control flow - for example by passing a function through a
>> channel to an existing goroutine that calls the function.
>
>
> I haven't thought about that. However, while this would be an interesting
> (and potentially confusing) interplay between dynamic and lexical scoping
> (employed by closures), I don't think it "doesn't work". A dynamically
> scoped variable is bound to the stack it's executing on. It's conceptually
> equivalent to sending a func(context.Context) over a channel and calling it
> with the context.Context of the receiver. It works just fine.

Yeah, by "doesn't work", I meant that the request context would not
propagate as expected in that kind of scenario. That means that we'd
be less free to refactor code to use arbitrary goroutines because it
might break things.

One property that I really appreciate about Go as is is that goroutine
use can be an entirely local decision that I can make without worrying
about breaking invariants in the larger system.

Sam Vilain

unread,
Aug 14, 2017, 2:20:45 PM8/14/17
to Axel Wagner, golang-nuts
Interesting, Alex - almost identical to my recent proposal, with similar conclusions about a cancellation variable.  The main difference is that in my proposal you have to declare it in each scope where you'd use it, which means by default it gets the zero value - but they're both scoped by the call stack with a new value on re-assignment, and named by a package.  I could get behind either.

Sam

Jesper Louis Andersen

unread,
Aug 15, 2017, 7:44:39 AM8/15/17
to Sam Vilain, Axel Wagner, golang-nuts
The key point in Go is rather simple:

Since goroutines clean up for themselves, and since goroutines are not isolated from each other, memory-wise, you need an explicit cancellation system. And programmers must abide by its rule for the correct operation of the program.

Programs communicate over a hypergraph of channels. They also live in a tree of contexts. We use channels for both of these two (stratified) phenomena, which is a bit unwieldy. But any solution for Go would still have to keep the explicitness of cancellation as per the above points.

Erlang (and Elixir) can handle implicit cancellation because processes are isolated and you can monitor the lifetime of a process. But that isolation means you need to copy data between processes, either physically or logically (since the data is immutable/persistent in a functional language, this is a lot easier than you may think).



To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages