// Wait on the channel, or for timeout
select {
case fd := <-channel:
return fd, nil
case <-time.After(queue.timeout):
return nil, ErrTimeoutElapsed
}
channel <- fd
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAMV2RqoMh-4w%3DxuFD-1Rk_BOzdDgS%2BiHygNYDBw55me0Sg-eGQ%40mail.gmail.com.
In my opinion the best place for this kind of discussion is a blog
post or talk.
And not only that, it's complicated. The language spec is not the
right place to dig into the complexities of how to use select safely
while avoiding race conditions. There is just too much to say. And
there are no docs for select other than the language spec.
If a select statement has multiple channels ready when it runs, then it will choose one at a random. So if you fire something across a channel that holds a resource, like an open file descriptor - you have no guarantees that the other end of the channel receives it. The (possibly full) channel will get garbage collected later and the resource will leak in that case.
If the channel is unbuffered, there are two parties, S and R (Sender and Receiver). If the channel is buffered, it is another party, C (channel). The delivery chain is really S -> C -> R. Whereas in the unbuffered case, rendezvous means an atomic exchange of the resource (S -> R). Clearly, cleanup is the responsibility of the owner at any point in time. But the extra owner, C, means that you will have to handle the case where the resource is in limbo between the two systems. Since a channel cannot run code, you will have to either let S or R handle it, or introduce a proxy, P, who handles eventual cleanup on behalf of C.
--
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.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CADz32d%3DOm96%2B7iZet%3DDL0AaNxYVYWO6Q%3DOgvzoYiWKdZpSipHg%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.
I couldn't use <-channel.Close since in my case the goroutine isn't guaranteed to have something sent, so that would leak goroutines. I added a cleanup goroutine to scan timed-out channels and close them, which solves this problem. But now I can use that close as a signal to the receiver than the a timeout happened, and eliminate the select and the race entirely. The close can in rare cases race with the sender, but that's easily enough fixed:// TrySend tries to send on a possibly closed channel and handles the panic if necessary.
// Returns true if conn was successfully sent over the channel.
func (waiter *Waiter) TrySend(conn Connection) (sent bool) {
defer func() {
r := recover()
sent = r != nil
}()
waiter.channel <- conn
return}
So I guess the best thing to do in these cases is don't combine select with sending unmanaged resources over a channel. It's probably worth warning about this problem in the docs for select? It's not an obvious gotcha.
unbuffered means nothing is sent until is is simultaneously received, so there is no limbo or race or uncertainty. one sender "wins" the select and the others remain blocked waiting.
On Wed, Jul 10, 2019 at 7:54 AM Michael Jones <michae...@gmail.com> wrote:unbuffered means nothing is sent until is is simultaneously received, so there is no limbo or race or uncertainty. one sender "wins" the select and the others remain blocked waiting.So I'm correct then: "Now one of two things must happen, either the sender blocks forever because nobody read the sent value"
The best thing to do is to not use unmanaged resources.
Yeah, agreed. I've been deep into concurrent programming for a long time now, and into lock-free programming as well which is the most fraught kind of programming I've ever done. Parallel is the future, it has been that way for a long time, but it's only getting more and more obvious.I think in this specific case, the timeout should have been handled on the sending side in a select, almost identically to the receiver code I posted. If the timer channel triggers, then close the channel to indicate to the receiver that it can wake up and it has timed out. Then the sender can go ahead and clean up the resource which it still owns. Doing it on the receiver side is fraught with problems.I solved it with a dedicated go routine that scans for timed out waiters and expires them by closing the channel, but that meant the sender now needs to handle the rare panic if it sends on a closed channel - not the end of the world, but not as clean.