Skip to content

Commit eaaccff

Browse files
committed
fix: race in Single.Do
1 parent e81f3a9 commit eaaccff

File tree

1 file changed

+15
-10
lines changed

1 file changed

+15
-10
lines changed

common/singledo/singledo.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,26 @@ type call[T any] struct {
1313

1414
type Single[T any] struct {
1515
mux sync.Mutex
16-
last time.Time
1716
wait time.Duration
1817
call *call[T]
1918
result *Result[T]
2019
}
2120

2221
type Result[T any] struct {
23-
Val T
24-
Err error
22+
Val T
23+
Err error
24+
Time time.Time
2525
}
2626

2727
// Do single.Do likes sync.singleFlight
2828
func (s *Single[T]) Do(fn func() (T, error)) (v T, err error, shared bool) {
2929
s.mux.Lock()
30-
now := time.Now()
31-
if now.Before(s.last.Add(s.wait)) {
30+
result := s.result
31+
if result != nil && time.Since(result.Time) < s.wait {
3232
s.mux.Unlock()
33-
return s.result.Val, s.result.Err, true
33+
return result.Val, result.Err, true
3434
}
35+
s.result = nil // The result has expired, clear it
3536

3637
if callM := s.call; callM != nil {
3738
s.mux.Unlock()
@@ -47,15 +48,19 @@ func (s *Single[T]) Do(fn func() (T, error)) (v T, err error, shared bool) {
4748
callM.wg.Done()
4849

4950
s.mux.Lock()
50-
s.call = nil
51-
s.result = &Result[T]{callM.val, callM.err}
52-
s.last = now
51+
if s.call == callM { // maybe reset when fn is running
52+
s.call = nil
53+
s.result = &Result[T]{callM.val, callM.err, time.Now()}
54+
}
5355
s.mux.Unlock()
5456
return callM.val, callM.err, false
5557
}
5658

5759
func (s *Single[T]) Reset() {
58-
s.last = time.Time{}
60+
s.mux.Lock()
61+
s.call = nil
62+
s.result = nil
63+
s.mux.Unlock()
5964
}
6065

6166
func NewSingle[T any](wait time.Duration) *Single[T] {

0 commit comments

Comments
 (0)