The sync.Locker used with a sync.Cond must be acquired when changing
the associated condition, otherwise there is a window within
sync.Cond.Wait() where a wake-up may be missed.
Fixes: 4846070 ("device: use a waiting sync.Pool instead of a channel")
Reviewed-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Signed-off-by: Jordan Whited <jordan@tailscale.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
import (
"sync"
- "sync/atomic"
)
type WaitPool struct {
pool sync.Pool
cond sync.Cond
lock sync.Mutex
- count atomic.Uint32
+ count uint32 // Get calls not yet Put back
max uint32
}
func (p *WaitPool) Get() any {
if p.max != 0 {
p.lock.Lock()
- for p.count.Load() >= p.max {
+ for p.count >= p.max {
p.cond.Wait()
}
- p.count.Add(1)
+ p.count++
p.lock.Unlock()
}
return p.pool.Get()
if p.max == 0 {
return
}
- p.count.Add(^uint32(0))
+ p.lock.Lock()
+ defer p.lock.Unlock()
+ p.count--
p.cond.Signal()
}
wg.Add(workers)
var max atomic.Uint32
updateMax := func() {
- count := p.count.Load()
+ p.lock.Lock()
+ count := p.count
+ p.lock.Unlock()
if count > p.max {
t.Errorf("count (%d) > max (%d)", count, p.max)
}