})
}
+// https://go.dev/issue/74386
+func TestWaitGroupRacingAdds(t *testing.T) {
+ synctest.Run(func() {
+ var wg sync.WaitGroup
+ for range 100 {
+ wg.Go(func() {})
+ }
+ wg.Wait()
+ })
+}
+
func TestWaitGroupOutOfBubble(t *testing.T) {
var wg sync.WaitGroup
wg.Add(1)
})
}
-func TestWaitGroupMovedBetweenBubblesWithZeroCount(t *testing.T) {
+func TestWaitGroupDisassociateInWait(t *testing.T) {
var wg sync.WaitGroup
synctest.Run(func() {
wg.Add(1)
wg.Done()
+ // Count and waiters are 0, so Wait disassociates the WaitGroup.
+ wg.Wait()
})
synctest.Run(func() {
- // Reusing the WaitGroup is safe, because its count is zero.
+ // Reusing the WaitGroup is safe, because it is no longer bubbled.
wg.Add(1)
wg.Done()
})
}
-func TestWaitGroupMovedBetweenBubblesAfterWait(t *testing.T) {
+func TestWaitGroupDisassociateInAdd(t *testing.T) {
var wg sync.WaitGroup
synctest.Run(func() {
- wg.Go(func() {})
- wg.Wait()
+ wg.Add(1)
+ go wg.Wait()
+ synctest.Wait() // wait for Wait to block
+ // Count is 0 and waiters != 0, so Done wakes the waiters and
+ // disassociates the WaitGroup.
+ wg.Done()
})
synctest.Run(func() {
- // Reusing the WaitGroup is safe, because its count is zero.
- wg.Go(func() {})
- wg.Wait()
+ // Reusing the WaitGroup is safe, because it is no longer bubbled.
+ wg.Add(1)
+ wg.Done()
})
}
if w != 0 && delta > 0 && v == int32(delta) {
panic("sync: WaitGroup misuse: Add called concurrently with Wait")
}
- if v == 0 && bubbled {
- // Disassociate the WaitGroup from its bubble.
- synctest.Disassociate(wg)
- if w == 0 {
- wg.state.Store(0)
- }
- }
if v > 0 || w == 0 {
return
}
}
// Reset waiters count to 0.
wg.state.Store(0)
+ if bubbled {
+ // Adds must not happen concurrently with wait when counter is 0,
+ // so we can safely disassociate wg from its current bubble.
+ synctest.Disassociate(wg)
+ }
for ; w != 0; w-- {
runtime_Semrelease(&wg.sema, false, 0)
}
for {
state := wg.state.Load()
v := int32(state >> 32)
- w := uint32(state)
+ w := uint32(state & 0x7fffffff)
if v == 0 {
// Counter is 0, no need to wait.
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(wg))
}
+ if w == 0 && state&waitGroupBubbleFlag != 0 && synctest.IsAssociated(wg) {
+ // Adds must not happen concurrently with wait when counter is 0,
+ // so we can disassociate wg from its current bubble.
+ if wg.state.CompareAndSwap(state, 0) {
+ synctest.Disassociate(wg)
+ }
+ }
return
}
// Increment waiters count.