OK, I see where you're coming from - and I agree, this is a difficult one!

The point you were making is that

                                if g.ForgetUnshared(key) {
                                        cancel()
                                }

should only invoke cancel() if this result wasn't shared: i.e. there's only 
one receiver in the c.chans array, and c.dups == 0.  So where's the race, 
given that everything in g is done under a mutex?

What I have discovered so far is: when g.ForgetUnshared(key) returns true 
and the problem occurs, the key is not present in the map (as opposed to 
being present with c.dups == 0).  But I've not been able to work out why 
yet.

Incidentally, a minor style observation: you passed in ctx to your go 
func(...), but not cancel. As far as I can see, both ctx and cancel are 
local variables which drop immediately out of scope - there's no way they 
can be modified later outside of the goroutine.  So I believe you don't 
need to pass ctx at all: you can access it via the closure.  But if you do 
pass one "to be on the safe side", then I think the other should be passed 
as well - otherwise it's confusing why you passed in only one.

In fact, in this case, you could move the ctx/cancel creation inside the go 
func(...) anyway.  The only thing which needs to be outside is 
the wg.Add(1).

On Thursday, 22 September 2022 at 03:12:47 UTC+1 atomic wrote:

> > Also notice that the random time you pick for cancelTime can be longer 
> than the different random time you sleep inside the goroutine (i.e. the 
> function which you pass to DoChan).  Hence the goroutine can return a 
> result, before the cancelTime is reached.
>
> Although the goroutine can return a result before cancelTime arrives, the 
> returned result should not be err because I haven't had time to call 
> cancel(). 
> 在2022年9月21日星期三 UTC+8 20:18:30<Brian Candler> 写道:
>
>> Notice that DoChan starts a goroutine for the task...
>>
>>         go g.doCall(c, key, fn)
>>
>> ... and then returns immediately.
>>
>> Also notice that the random time you pick for cancelTime can be longer 
>> than the different random time you sleep inside the goroutine (i.e. the 
>> function which you pass to DoChan).  Hence the goroutine can return a 
>> result, before the cancelTime is reached.
>>
>> Try this modification:
>>
>> --- main.go.orig    2022-09-21 13:14:10.000000000 +0100
>> +++ main.go    2022-09-21 13:13:43.000000000 +0100
>> @@ -144,7 +144,7 @@
>>              defer wg.Done()
>>
>>              ch, _ := g.DoChan(key, func() (interface{}, error) {
>> -                time.Sleep(randTimeout())
>> +                time.Sleep(5000 * time.Millisecond)
>>                  if ctx.Err() == context.Canceled {
>>                      return nil, fmt.Errorf("callUUID=[%d] err=[%s]", 
>> uuid, ctx.Err())
>>                  }
>> @@ -152,7 +152,7 @@
>>              })
>>
>>              // randomly choose a timeout to cancel
>> -            cancelTime := time.After(randTimeout())
>> +            cancelTime := time.After(10 * time.Millisecond)
>>              select {
>>              case <-cancelTime:
>>                  // cancel only if no other goroutines share
>>
>> On Wednesday, 21 September 2022 at 10:01:22 UTC+1 atomic wrote:
>>
>>> Thanks for your reply, but I still don't understand why time.Sleep is 
>>> causing my test program to panic.
>>>
>>> In fact, this is a real online environment problem. My application uses 
>>> http.Client.Do(), but it occasionally has errors: [lookup xxxxx on 
>>> xxxxx: dial udp xxxxx: operation was canceled], after looking at the code, 
>>> I found that it may be There is a problem with ForgetUnshared, lookupIPAddr 
>>> uses ForgetUnshared: 
>>> https://github.com/golang/go/blob/4a4127bccc826ebb6079af3252bc6bfeaec187c4/src/net/lookup.go#L336
>>>
>>> 在2022年9月21日星期三 UTC+8 16:17:35<cuong.m...@gmail.com> 写道:
>>>
>>>> Hello,
>>>>
>>>> You use time.Sleep in your program, so the behavior is not predictable. 
>>>> In fact, I get it success or panic randomly.
>>>>
>>>> You can see https://go-review.googlesource.com/c/sync/+/424114 to see 
>>>> a predictable test of ForgetUnshared .
>>>>
>>>> On Wednesday, September 21, 2022 at 1:45:24 PM UTC+7 atomic wrote:
>>>>
>>>>> hello
>>>>>
>>>>> I find that the `src/internal/singleflight/singleflight.go 
>>>>> ForgetUnshared()` method returns results that are not always expected
>>>>>
>>>>> For this I wrote a test code, I copied the code in the 
>>>>> src/internal/singleflight/singleflight.go file to the main package, and 
>>>>> wrote a main function to test it, if ForgetUnshared() returns correctly, 
>>>>> this code It should not panic, but the fact that it will panic every time 
>>>>> it runs, is there something wrong with my understanding of 
>>>>> ForgetUnshared()?
>>>>>
>>>>> The test code cannot be run in goplay, so I posted a link: 
>>>>> https://gist.github.com/dchaofei/e07547bce17d94c3e05b1b2a7230f62f
>>>>>
>>>>> The go version I use for testing is 1.16, 1.19.1
>>>>>
>>>>> result:
>>>>> ```
>>>>> $ go run cmd/main.go
>>>>> panic: callUUID=[9314284969 <(931)%20428-4969>] err=[context 
>>>>> canceled] currentUUId=[6980556786]
>>>>> ```
>>>>>
>>>>

-- 
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/a6b236d0-da4d-41a3-bfa7-f120a8cdbe96n%40googlegroups.com.

Reply via email to