Why is Context explicit vs. implicit in Go?

218 views
Skip to first unread message

David Suarez

unread,
Jan 24, 2019, 3:54:28 PM1/24/19
to golang-nuts
First off, I am really loving Go overall.  I probably did what many do and started out with less Context use and now it is becoming a standard for a few tiers which I see was in a blog somewhere from Google as well.  What I haven't been able to find is if there has ever been a conversation on making it implicit vs. explicitly passed in method calls.  In java you can do this to grab, set a context variable and then pull it a few steps down the request cycle without explicitly adding it to method signatures in between.  Is there a specific reason it is not implicit (e.g. readability or other) or just a conversation that hasn't happened yet?  Should it be a feature request to minimize its use to only those areas it is really needed? 

Ian Denhardt

unread,
Jan 24, 2019, 4:08:10 PM1/24/19
to David Suarez, golang-nuts
There's actually been a fair amount of conversation on this topic
lately. Most recently it came up in this thread:

https://groups.google.com/forum/#!searchin/golang-nuts/context|sort:date/golang-nuts/Lqoj5bNQxzg/5LUEYGHAGQAJ

My biggest worry about making it implicit is that it becomes hard to
tell what APIs actually respect cancellation via context. The context
package was introduced at a point when the language was already fairly
mature, so much of the standard library and ecosystem doesn't accept
context parameters, thus providing no (standard) way of canceling those
operations (though some packages have their own ad-hoc mechanisms).

I think if you were going to make the context implicit, to keep things
sane you'd also have to somehow ensure cancellation is "always"
respected. I know of two languages that provide this sort of mechanism
-- Erlang and Haskell -- but they do this by means of asynchronous
exceptions, which besides having their own pitfalls in the abstract,
would completely break essentially all of the resource cleanup code
written in Go today, by introducing race conditions, not to mention
fundamentally change how error handling has to work.

People have pointed out real problems with the current situation, but I
haven't seen a good solution yet.

-Ian

Quoting David Suarez (2019-01-24 12:02:15)
> its use to only those areas it is really needed?�
>
> --
> 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 [1]golang-nuts...@googlegroups.com.
> For more options, visit [2]https://groups.google.com/d/optout.
>
> Verweise
>
> 1. mailto:golang-nuts...@googlegroups.com
> 2. https://groups.google.com/d/optout

David Suarez

unread,
Jan 24, 2019, 6:55:02 PM1/24/19
to golang-nuts
Thank you for the pointer to the details and the super fast reply!  

It gives me what I need for now and I will basically just have service/ data tiers carry context and not models as the solution which is not super-difficult.  I wonder if splitting the concerns of cancellation and carrying contextual data (e.g. distributed tracing) into 2 separate concepts may be an easier way to solve the problem.  That may add unnecessary complexity in having 2 "context like" objects but it might also separate responsibilities into problems that can be solved independently with fit-for-purpose solutions?

Just a thought, thanks again for the quick response!  

PS>  Your summary is perfect, BTW.  Love how you captured the key points of all the detailed conversation in a well condensed and understandable manner.

Jason E. Aten

unread,
Jan 27, 2019, 7:09:24 AM1/27/19
to golang-nuts
Often times I don't need all the elaborate machinery and verbosity of context. 

For simple things like stopping a goroutine, I usually use a slight elaboration on closing a channel. 

This is a pattern I call "idempotent close".  I've wrapped it up in a little library called "idem". Here is an illustration:

a) the main state struct for the goroutine always has a member called Halt.


type GoroutineState struct {
  Halt *idem.Halter
}

func NewGoroutineState() *GoroutineState {
    return &GoroutineState {
        Halt: idem.NewHalter(),
    }
}

b) The state machine for the goroutine is typically launched from a Start() function; it checks for shutdown with

func (g *GoroutineState) Start() {
   go func() {
      for {
           select {
                case <- g.Halt.ReqStop.Chan:
                     g.Halt.MarkDone()
                     return
                case... // the rest of the state machine logic
           }
       }
   }()
}

c) Any number of clients can then safely shutdown the goroutine g:

g.Halt.RequestStop()
<- g.Halt.Done.Chan // optional, but use this if you need to know g is done

Reply all
Reply to author
Forward
0 new messages