Hey,

On Mon, Sep 9, 2019 at 9:55 AM changkun <euryugas...@gmail.com> wrote:

> Here comes with more interesting points. My "intuitive" comes from this
> scenario:
> I have an unbuffered channel "closedCh" that indicates if a network
> connection is closed, a timer handles for heartbeat detection and polls
> some data, if a connection is closed, then returns an error.
>
> At some point, I close the "closedCh" for the termination of previous
> for-select loop.
> I initially thought that the heartbeat detection case will not be executed
> and
> closedCh will safely return and terminates the loop.
> However, there are some "false negative" results returned by the loop,
> because select still can select the heartbeat case if the heartbeat and
> closedCh arrive concurrently.
>

That's fair and I have to admit that I ran into similar situations once or
twice in the past without having a super good solution. FWIW, I think one
hindrance is that network interactions are usually inherently racey. You
can't really know or guarantee when and in what order packages arrive. So
it might be a good idea to think about a way to prevent this from causing
issues anyway. For example, I would probably just try writing/reading the
heartbeat and if an error occurs while doing that, follow that with a check
if the connection was already closed. e.g. something like

case <- ticker.C:
    err := heartbeat()
    if err == nil {
        continue
    }
    select {
    case <-closedCh:
        // connection was closed properly, error is expected
        return nil
    default:
        return err
    }
}

this means that you *might* sometimes send an extra heartbeat message, but
that's why I'm mentioning that you can't fully prevent that in practice
anyway, so the receiver needs to be able to cope.

Note in particular, that your code remains racey. No matter *how* you
implement the check for closed-nes (including your atomic variable), the
close could always happen right after your check and before you do the
heartbeat processing. It's an inherently asynchronous problem :)

Best,

Axel

PS: Don't worry about the name, it happens *all* the time and I'm no longer
bothered :)


See the following code for TLDR:
>
> // goroutine1
> closedCh := make(chan struct{})
>
> // do some preparation
> ...
>
> for {
>     select {
>     case <- ticker.C:
>         // it is possible that this case can still being executed
>         // one more time if closedCh arrives.
>
>         // heartbeat detection, data processing..
>         ...
>
>     case <- closedCh
>         return
> }
>
>
>
> // goroutine2
> close(closedCh)
>
> To fix the issue I have encountered, I have to use an atomic value for
> double check
> if the channel is closed inside the ticker case:
>
> // "global" data
> closedCh := make(chan struct{})
> closed := uint32(0)
>
> // goroutine1
>
> // do some preparation
> ...
>
> for {
>     select {
>     case <- ticker.C:
>         // now it is ok
>         if atomic.LoadUint32(&closed) == 1 {
>             return
>         }
>
>         // heartbeat detection, data processing..
>         ...
>
>     case <- closedCh
>         return
> }
>
>
>
> // goroutine2
> if !atomic.CompareAndSwapUint32(&closed, 0, 1) {
>     return ErrClosed
> }
> close(closedCh)
>
> which does not like an ideal solution (not sure if there is a better
> way?), because for close a ticker,
> I need an atomic value and an unbuffered channel that appear in three
> different places.
>
>
>>
>> Now, ISTM that the simplest intuitive interpretation of what "event A and
>> B happen concurrently" (defined abstractly as "are not ordered in the
>> happens-before partial order) is that the order that A and B happen in real
>> time in is unclear and could happen either way olin practice. And under
>> that intuitive understanding, I don't know how you conclude that the select
>> should prioritize either or not execute the ticker case at all. After all -
>> you can never know whether the ticker *has actually fired* at the concrete
>> real point in time the select executed.
>>
> Sorry for lack of problem statement, see above
>
>
>>
>> Note, by the way, that the fact that you observe either result doesn't
>> actually say anything about the pseudo-randomness or lack thereof of the
>> select statement: You can't, from the output of the program, distinguish
>> between "both cases where ready to proceed and select flipped a coin coming
>> up close" from "only close was ready to proceed, because select executed in
>> between the closing/firing" (and the same for the timer firing). The
>> pseudo-randomness of select is irrelevant for your case, it's the fact that
>> these events are concurrent, both as specified and IMO as intuitively
>> obvious.
>>
> You are right, it is not a suitable example for the question I have. I
> feel sorry about it. I hope you didn't get trouble for reading it.
> In fact, I am curious: if select work with a random selection, is it
> possible that a case will never be executed?
> How can select statement provide fairness similar to the FIFO-semantic
> channel (https://github.com/golang/go/issues/11506)?
>
>
> --
> 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+unsubscr...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/golang-nuts/7736fea9-b4c0-4b1b-8b0e-9bc3b0e3bd7b%40googlegroups.com
> <https://groups.google.com/d/msgid/golang-nuts/7736fea9-b4c0-4b1b-8b0e-9bc3b0e3bd7b%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>

-- 
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+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/CAEkBMfHOA9d47i3V1m8fORH-bfamFF1c_jiN3F%2B3rzJRQpmcrg%40mail.gmail.com.

Reply via email to