Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: Go 2.0: Mechanism to bind a channel to a goroutine #25821

Closed
GeorgeMac opened this issue Jun 11, 2018 · 4 comments
Closed

proposal: Go 2.0: Mechanism to bind a channel to a goroutine #25821

GeorgeMac opened this issue Jun 11, 2018 · 4 comments
Labels
FrozenDueToAge Proposal v2 A language change or incompatible library change
Milestone

Comments

@GeorgeMac
Copy link

GeorgeMac commented Jun 11, 2018

A quick note I tried to see if this has been proposed already. Haven't found anything, so taking a stab at sharing my thoughts 馃檱 Sorry if this has been suggested before.

Proposal

To be able to declare (most commonly when defining another functions receiving argument types) that the channel rference being passed to a function is now bound to that goroutine. This means that function could not communicate that reference outside of its own routine. i.e. it could not pass the channel to a spawned routine or pass that channel via other means, causing it to leak into another routine.

The following is a contrived example, with likely a poor user experience. But I think it captures the intent:

func foo(ch ^chan string) {
  go func() {
    ch <- "thing" // would fail compilation as channel has leaked into another routine
  }
}

Here I use the caret (^) to signify this channel reference will become bound to the routine it is called within. I am sure there exists a clearer user-experience for this feature.

Motivation

This is to aid in defining well structured code, which does not send on a closed channel.

This is a safety mechanism in the spirit of the direction of a channel e.g. func foo(ch chan<- string). I would expect it to be used in the same fashion. This new compile time enforced rule can be used to uphold best practice for handing channels off to routines which will send on them.

Often after constructing a channel, it is handed off to other functions. Ideally divided into some which produce and some which consume. When clearing up and ensuring all processes have finished, we often want to inform consumers via closing the channel in question and then waiting for those consumers to exit. However, before we can safely close the channel, we must first ensure that the producers will not attempt to send again. Otherwise, the subsequent send on the closed channel will panic.

One way to do this is to interrupt the producers and wait for them to return. This can be achieved via cancelling a context.Context and having the producers pay attention to ctxt.Done() for example. Waiting for the producing routines doesn't give the caller complete guarantee that the channel won't get sent on again however. If the producer spawned anymore routines with that channel or worse, sent that channel on another channel 馃槰 to another routine, then a send could occur again.

A mechanism like the one described would ensure that producers could be defined to have channels which are "bound" to the goroutine they are executed within. This gives a guarantee to the invokers of the producing functions that once the function returns nothing that it touched can send on the channel again.

I would expect that (given the caret syntax above as an example again) this would often be used in conjunction with the direction syntax. e.g. func bar(ch ^chan<- string) which is a function which receives a send-only channel bound to the current routine.

This may not be feasible, but thought I would share the idea. I am probably asking for something extremely difficult to enforce or check at compile time.

@gopherbot gopherbot added this to the Proposal milestone Jun 11, 2018
@GeorgeMac
Copy link
Author

GeorgeMac commented Jun 11, 2018

The more I think about this, the more the devilish details present themselves.

  1. How to deal with the fact that a channel can be shared by other means. e.g. a global variable
    Perhaps you would have to enforce a compile time rule to not allow bound channels to be assigned to variable.
  2. How to deal with the channel being embedded in something else which can be shared. e.g. a struct or a closure.
    I guess this could create some complex kind of labelling required internally to say that a thing owns a channel which is bound, therefore, it is also bound by the same rules.

This leads me to wonder if this mechanism would be more favorable as a generic mechanism for labelling any type as "bound". Which I guess is a bigger problem which has probably been defined more eloquently in another proposal 馃槀

If this is the case, I hope the example I described at least becomes a useful use-case for further pondering :)

@GeorgeMac GeorgeMac changed the title proposal: Go 2.0: Mechanism to bind a channel to a routine proposal: Go 2.0: Mechanism to bind a channel to a goroutine Jun 11, 2018
@GeorgeMac
Copy link
Author

GeorgeMac commented Jun 11, 2018

To better illustrate my thoughts. Here is a more useful (yet still contrived) example:

Say I defined a type which took and executed some other piece of functionality via receiving either a func or an interface. Let us say that those functions or the functions defined within the interface took parameters which were bound or "pinned" to their callee goroutine:

type StringProducerFunc func(ch ^chan<- string)

func SendStrings(ch ^chan<- string) {
  for { ch <- "words" }
}

func Do10TimesConcurrently(fn StringProducerFunc) {
    ch := make(chan string)
    wg.Add(10)
    for i := 0; i < 10; i++ {
        go func(){
          defer wg.Done()
          fn(ch)
        }()
    }
    wg.Done()
    close(ch)
}

func main() { Do10TimeConcurrently(SendStrings) }

The do 10 times concurrently could feel safe that closing the channel ch will not panic if it is protected at compile time from that channel being shared elsewhere by SendStrings.

@FiloSottile FiloSottile added the v2 A language change or incompatible library change label Jun 11, 2018
@ianlancetaylor
Copy link
Contributor

You are proposing a kind of type qualifier. Within a function that uses a channel type with that qualifier, values of that type may not be sent to a different goroutine. But of course the channel value can be sent to a different goroutine before entering that function; channels are only useful if you can send them to other goroutines. And detecting whether a channel value is sent to another goroutine is non-trivial, as you note above. You also don't discuss whether a value of type ^chan int can be assigned to a value of type chan int; presumably not, but that means that you can't use these channels with any helper functions. It's a form of const poisoning, only in this case it is channel-within-goroutine poisoning.

In general Go avoids type qualifiers. We would only add a new type qualifier for a general problem encountered by many programs. This proposal seems to be for a very specific problem that most programs do not encounter.

We aren't going to do this, but thanks for the suggestion.

@GeorgeMac
Copy link
Author

Thank you for the explanation. Very much appreciated :)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge Proposal v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests

4 participants