pickerwrapper: use atomic instead of locks (#7214)

This commit is contained in:
Doug Fawley 2024-05-16 13:39:10 -07:00 committed by GitHub
parent 0020ccf9d9
commit e22436abb8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 42 additions and 43 deletions

View File

@ -198,6 +198,10 @@ func (ccb *ccBalancerWrapper) UpdateAddresses(sc balancer.SubConn, addrs []resol
func (ccb *ccBalancerWrapper) UpdateState(s balancer.State) { func (ccb *ccBalancerWrapper) UpdateState(s balancer.State) {
ccb.cc.mu.Lock() ccb.cc.mu.Lock()
defer ccb.cc.mu.Unlock() defer ccb.cc.mu.Unlock()
if ccb.cc.conns == nil {
// The CC has been closed; ignore this update.
return
}
ccb.mu.Lock() ccb.mu.Lock()
if ccb.closed { if ccb.closed {

View File

@ -22,7 +22,7 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"sync" "sync/atomic"
"google.golang.org/grpc/balancer" "google.golang.org/grpc/balancer"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
@ -33,35 +33,43 @@ import (
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )
// pickerGeneration stores a picker and a channel used to signal that a picker
// newer than this one is available.
type pickerGeneration struct {
// picker is the picker produced by the LB policy. May be nil if a picker
// has never been produced.
picker balancer.Picker
// blockingCh is closed when the picker has been invalidated because there
// is a new one available.
blockingCh chan struct{}
}
// pickerWrapper is a wrapper of balancer.Picker. It blocks on certain pick // pickerWrapper is a wrapper of balancer.Picker. It blocks on certain pick
// actions and unblock when there's a picker update. // actions and unblock when there's a picker update.
type pickerWrapper struct { type pickerWrapper struct {
mu sync.Mutex // If pickerGen holds a nil pointer, the pickerWrapper is closed.
done bool pickerGen atomic.Pointer[pickerGeneration]
blockingCh chan struct{}
picker balancer.Picker
statsHandlers []stats.Handler // to record blocking picker calls statsHandlers []stats.Handler // to record blocking picker calls
} }
func newPickerWrapper(statsHandlers []stats.Handler) *pickerWrapper { func newPickerWrapper(statsHandlers []stats.Handler) *pickerWrapper {
return &pickerWrapper{ pw := &pickerWrapper{
blockingCh: make(chan struct{}),
statsHandlers: statsHandlers, statsHandlers: statsHandlers,
} }
pw.pickerGen.Store(&pickerGeneration{
blockingCh: make(chan struct{}),
})
return pw
} }
// updatePicker is called by UpdateBalancerState. It unblocks all blocked pick. // updatePicker is called by UpdateState calls from the LB policy. It
// unblocks all blocked pick.
func (pw *pickerWrapper) updatePicker(p balancer.Picker) { func (pw *pickerWrapper) updatePicker(p balancer.Picker) {
pw.mu.Lock() old := pw.pickerGen.Swap(&pickerGeneration{
if pw.done { picker: p,
pw.mu.Unlock() blockingCh: make(chan struct{}),
return })
} close(old.blockingCh)
pw.picker = p
// pw.blockingCh should never be nil.
close(pw.blockingCh)
pw.blockingCh = make(chan struct{})
pw.mu.Unlock()
} }
// doneChannelzWrapper performs the following: // doneChannelzWrapper performs the following:
@ -98,20 +106,17 @@ func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer.
var lastPickErr error var lastPickErr error
for { for {
pw.mu.Lock() pg := pw.pickerGen.Load()
if pw.done { if pg == nil {
pw.mu.Unlock()
return nil, balancer.PickResult{}, ErrClientConnClosing return nil, balancer.PickResult{}, ErrClientConnClosing
} }
if pg.picker == nil {
if pw.picker == nil { ch = pg.blockingCh
ch = pw.blockingCh
} }
if ch == pw.blockingCh { if ch == pg.blockingCh {
// This could happen when either: // This could happen when either:
// - pw.picker is nil (the previous if condition), or // - pw.picker is nil (the previous if condition), or
// - has called pick on the current picker. // - we have already called pick on the current picker.
pw.mu.Unlock()
select { select {
case <-ctx.Done(): case <-ctx.Done():
var errStr string var errStr string
@ -145,9 +150,8 @@ func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer.
} }
} }
ch = pw.blockingCh ch = pg.blockingCh
p := pw.picker p := pg.picker
pw.mu.Unlock()
pickResult, err := p.Pick(info) pickResult, err := p.Pick(info)
if err != nil { if err != nil {
@ -197,24 +201,15 @@ func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer.
} }
func (pw *pickerWrapper) close() { func (pw *pickerWrapper) close() {
pw.mu.Lock() old := pw.pickerGen.Swap(nil)
defer pw.mu.Unlock() close(old.blockingCh)
if pw.done {
return
}
pw.done = true
close(pw.blockingCh)
} }
// reset clears the pickerWrapper and prepares it for being used again when idle // reset clears the pickerWrapper and prepares it for being used again when idle
// mode is exited. // mode is exited.
func (pw *pickerWrapper) reset() { func (pw *pickerWrapper) reset() {
pw.mu.Lock() old := pw.pickerGen.Swap(&pickerGeneration{blockingCh: make(chan struct{})})
defer pw.mu.Unlock() close(old.blockingCh)
if pw.done {
return
}
pw.blockingCh = make(chan struct{})
} }
// dropError is a wrapper error that indicates the LB policy wishes to drop the // dropError is a wrapper error that indicates the LB policy wishes to drop the