Skip to content

Commit 67f06af

Browse files
committed
singleflight: fix flaky TestForget
There can be a race condition in current TestForget, that said when "close(secondCh)" is executed, the second goroutine will be finished immediately, causing the third "g.Do" is evaluated. To fix this, we change to use "g.DoChan" for both second and third. In second, we block to make sure it's still running at the time we call third. after then we unblock second and verify the result. Fixes golang/go#42092 Change-Id: I980fdf109a531e2b7a74c8149b4fcaa338775e08 Reviewed-on: https://go-review.googlesource.com/c/sync/+/263877 Trust: Cuong Manh Le <cuong.manhle.vn@gmail.com> Run-TryBot: Cuong Manh Le <cuong.manhle.vn@gmail.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Bryan C. Mills <bcmills@google.com>
1 parent b3e1573 commit 67f06af

File tree

1 file changed

+23
-52
lines changed

1 file changed

+23
-52
lines changed

singleflight/singleflight_test.go

Lines changed: 23 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -97,70 +97,41 @@ func TestDoDupSuppress(t *testing.T) {
9797
func TestForget(t *testing.T) {
9898
var g Group
9999

100-
var firstStarted, firstFinished sync.WaitGroup
100+
var (
101+
firstStarted = make(chan struct{})
102+
unblockFirst = make(chan struct{})
103+
firstFinished = make(chan struct{})
104+
)
101105

102-
firstStarted.Add(1)
103-
firstFinished.Add(1)
104-
105-
firstCh := make(chan struct{})
106106
go func() {
107107
g.Do("key", func() (i interface{}, e error) {
108-
firstStarted.Done()
109-
<-firstCh
110-
firstFinished.Done()
108+
close(firstStarted)
109+
<-unblockFirst
110+
close(firstFinished)
111111
return
112112
})
113113
}()
114+
<-firstStarted
115+
g.Forget("key")
114116

115-
firstStarted.Wait()
116-
g.Forget("key") // from this point no two function using same key should be executed concurrently
117-
118-
var secondStarted int32
119-
var secondFinished int32
120-
var thirdStarted int32
121-
122-
secondCh := make(chan struct{})
123-
secondRunning := make(chan struct{})
124-
go func() {
125-
g.Do("key", func() (i interface{}, e error) {
126-
defer func() {
127-
}()
128-
atomic.AddInt32(&secondStarted, 1)
129-
// Notify that we started
130-
secondCh <- struct{}{}
131-
// Wait other get above signal
132-
<-secondRunning
133-
<-secondCh
134-
atomic.AddInt32(&secondFinished, 1)
135-
return 2, nil
136-
})
137-
}()
138-
139-
close(firstCh)
140-
firstFinished.Wait() // wait for first execution (which should not affect execution after Forget)
141-
142-
<-secondCh
143-
// Notify second that we got the signal that it started
144-
secondRunning <- struct{}{}
145-
if atomic.LoadInt32(&secondStarted) != 1 {
146-
t.Fatal("Second execution should be executed due to usage of forget")
147-
}
117+
unblockSecond := make(chan struct{})
118+
secondResult := g.DoChan("key", func() (i interface{}, e error) {
119+
<-unblockSecond
120+
return 2, nil
121+
})
148122

149-
if atomic.LoadInt32(&secondFinished) == 1 {
150-
t.Fatal("Second execution should be still active")
151-
}
123+
close(unblockFirst)
124+
<-firstFinished
152125

153-
close(secondCh)
154-
result, _, _ := g.Do("key", func() (i interface{}, e error) {
155-
atomic.AddInt32(&thirdStarted, 1)
126+
thirdResult := g.DoChan("key", func() (i interface{}, e error) {
156127
return 3, nil
157128
})
158129

159-
if atomic.LoadInt32(&thirdStarted) != 0 {
160-
t.Error("Third call should not be started because was started during second execution")
161-
}
162-
if result != 2 {
163-
t.Errorf("We should receive result produced by second call, expected: 2, got %d", result)
130+
close(unblockSecond)
131+
<-secondResult
132+
r := <-thirdResult
133+
if r.Val != 2 {
134+
t.Errorf("We should receive result produced by second call, expected: 2, got %d", r.Val)
164135
}
165136
}
166137

0 commit comments

Comments
 (0)