clientv3: update eps if pinAddr is not included in updateAddrs

FIXES #7392
This commit is contained in:
fanmin shi 2017-03-14 15:42:56 -07:00
parent 5193965005
commit a23609efe6
2 changed files with 74 additions and 20 deletions

View File

@ -56,6 +56,9 @@ type simpleBalancer struct {
// donec closes when all goroutines are exited // donec closes when all goroutines are exited
donec chan struct{} donec chan struct{}
// updateAddrsC notifies updateNotifyLoop to update addrs.
updateAddrsC chan struct{}
// grpc issues TLS cert checks using the string passed into dial so // grpc issues TLS cert checks using the string passed into dial so
// that string must be the host. To recover the full scheme://host URL, // that string must be the host. To recover the full scheme://host URL,
// have a map from hosts to the original endpoint. // have a map from hosts to the original endpoint.
@ -76,14 +79,15 @@ func newSimpleBalancer(eps []string) *simpleBalancer {
} }
notifyCh <- addrs notifyCh <- addrs
sb := &simpleBalancer{ sb := &simpleBalancer{
addrs: addrs, addrs: addrs,
notifyCh: notifyCh, notifyCh: notifyCh,
readyc: make(chan struct{}), readyc: make(chan struct{}),
upc: make(chan struct{}), upc: make(chan struct{}),
stopc: make(chan struct{}), stopc: make(chan struct{}),
downc: make(chan struct{}), downc: make(chan struct{}),
donec: make(chan struct{}), donec: make(chan struct{}),
host2ep: getHost2ep(eps), updateAddrsC: make(chan struct{}, 1),
host2ep: getHost2ep(eps),
} }
go sb.updateNotifyLoop() go sb.updateNotifyLoop()
return sb return sb
@ -116,7 +120,6 @@ func (b *simpleBalancer) updateAddrs(eps []string) {
np := getHost2ep(eps) np := getHost2ep(eps)
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock()
match := len(np) == len(b.host2ep) match := len(np) == len(b.host2ep)
for k, v := range np { for k, v := range np {
@ -127,6 +130,7 @@ func (b *simpleBalancer) updateAddrs(eps []string) {
} }
if match { if match {
// same endpoints, so no need to update address // same endpoints, so no need to update address
b.mu.Unlock()
return return
} }
@ -137,13 +141,30 @@ func (b *simpleBalancer) updateAddrs(eps []string) {
addrs = append(addrs, grpc.Address{Addr: getHost(eps[i])}) addrs = append(addrs, grpc.Address{Addr: getHost(eps[i])})
} }
b.addrs = addrs b.addrs = addrs
// updating notifyCh can trigger new connections, // updating notifyCh can trigger new connections,
// but balancer only expects new connections if all connections are down // only update addrs if all connections are down
if b.pinAddr == "" { // or addrs does not include pinAddr.
b.notifyCh <- addrs update := !hasAddr(addrs, b.pinAddr)
b.mu.Unlock()
if update {
select {
case b.updateAddrsC <- struct{}{}:
case <-b.stopc:
}
} }
} }
func hasAddr(addrs []grpc.Address, targetAddr string) bool {
for _, addr := range addrs {
if targetAddr == addr.Addr {
return true
}
}
return false
}
func (b *simpleBalancer) updateNotifyLoop() { func (b *simpleBalancer) updateNotifyLoop() {
defer close(b.donec) defer close(b.donec)
@ -170,21 +191,28 @@ func (b *simpleBalancer) updateNotifyLoop() {
case <-b.stopc: case <-b.stopc:
return return
} }
case <-b.updateAddrsC:
b.notifyAddrs()
continue
} }
select { select {
case <-downc: case <-downc:
b.mu.RLock() b.notifyAddrs()
addrs := b.addrs case <-b.updateAddrsC:
b.mu.RUnlock() b.notifyAddrs()
select {
case b.notifyCh <- addrs:
case <-b.stopc:
return
}
case <-b.stopc: case <-b.stopc:
return return
} }
}
}
func (b *simpleBalancer) notifyAddrs() {
b.mu.RLock()
addrs := b.addrs
b.mu.RUnlock()
select {
case b.notifyCh <- addrs:
case <-b.stopc:
} }
} }
@ -198,6 +226,11 @@ func (b *simpleBalancer) Up(addr grpc.Address) func(error) {
if b.closed { if b.closed {
return func(err error) {} return func(err error) {}
} }
// gRPC might call Up on a stale address.
// Prevent updating pinAddr with a stale address.
if !hasAddr(b.addrs, addr.Addr) {
return func(err error) {}
}
if b.pinAddr == "" { if b.pinAddr == "" {
// notify waiting Get()s and pin first connected address // notify waiting Get()s and pin first connected address

View File

@ -71,6 +71,27 @@ func testDialSetEndpoints(t *testing.T, setBefore bool) {
cancel() cancel()
} }
// TestSwitchSetEndpoints ensures SetEndpoints can switch one endpoint
// with a new one that doesn't include original endpoint.
func TestSwitchSetEndpoints(t *testing.T) {
defer testutil.AfterTest(t)
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
defer clus.Terminate(t)
// get non partitioned members endpoints
eps := []string{clus.Members[1].GRPCAddr(), clus.Members[2].GRPCAddr()}
cli := clus.Client(0)
clus.Members[0].InjectPartition(t, clus.Members[1:])
cli.SetEndpoints(eps...)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if _, err := cli.Get(ctx, "foo"); err != nil {
t.Fatal(err)
}
}
func TestRejectOldCluster(t *testing.T) { func TestRejectOldCluster(t *testing.T) {
defer testutil.AfterTest(t) defer testutil.AfterTest(t)
// 2 endpoints to test multi-endpoint Status // 2 endpoints to test multi-endpoint Status