|
|
@@ -17,7 +17,7 @@ package clientv3
|
|
|
import (
|
|
|
"net/url"
|
|
|
"strings"
|
|
|
- "sync/atomic"
|
|
|
+ "sync"
|
|
|
|
|
|
"golang.org/x/net/context"
|
|
|
"google.golang.org/grpc"
|
|
|
@@ -26,32 +26,109 @@ import (
|
|
|
// simpleBalancer does the bare minimum to expose multiple eps
|
|
|
// to the grpc reconnection code path
|
|
|
type simpleBalancer struct {
|
|
|
- // eps are the client's endpoints stripped of any URL scheme
|
|
|
- eps []string
|
|
|
- ch chan []grpc.Address
|
|
|
- numGets uint32
|
|
|
+ // addrs are the client's endpoints for grpc
|
|
|
+ addrs []grpc.Address
|
|
|
+ // notifyCh notifies grpc of the set of addresses for connecting
|
|
|
+ notifyCh chan []grpc.Address
|
|
|
+
|
|
|
+ // readyc closes once the first connection is up
|
|
|
+ readyc chan struct{}
|
|
|
+ readyOnce sync.Once
|
|
|
+
|
|
|
+ // mu protects upEps, pinAddr, and connectingAddr
|
|
|
+ mu sync.RWMutex
|
|
|
+ // upEps holds the current endpoints that have an active connection
|
|
|
+ upEps map[string]struct{}
|
|
|
+ // upc closes when upEps transitions from empty to non-zero or the balancer closes.
|
|
|
+ upc chan struct{}
|
|
|
+
|
|
|
+ // pinAddr is the currently pinned address; set to the empty string on
|
|
|
+ // intialization and shutdown.
|
|
|
+ pinAddr string
|
|
|
}
|
|
|
|
|
|
-func newSimpleBalancer(eps []string) grpc.Balancer {
|
|
|
- ch := make(chan []grpc.Address, 1)
|
|
|
+func newSimpleBalancer(eps []string) *simpleBalancer {
|
|
|
+ notifyCh := make(chan []grpc.Address, 1)
|
|
|
addrs := make([]grpc.Address, len(eps))
|
|
|
for i := range eps {
|
|
|
addrs[i].Addr = getHost(eps[i])
|
|
|
}
|
|
|
- ch <- addrs
|
|
|
- return &simpleBalancer{eps: eps, ch: ch}
|
|
|
+ notifyCh <- addrs
|
|
|
+ sb := &simpleBalancer{
|
|
|
+ addrs: addrs,
|
|
|
+ notifyCh: notifyCh,
|
|
|
+ readyc: make(chan struct{}),
|
|
|
+ upEps: make(map[string]struct{}),
|
|
|
+ upc: make(chan struct{}),
|
|
|
+ }
|
|
|
+ return sb
|
|
|
+}
|
|
|
+
|
|
|
+func (b *simpleBalancer) Start(target string) error { return nil }
|
|
|
+
|
|
|
+func (b *simpleBalancer) Up(addr grpc.Address) func(error) {
|
|
|
+ b.mu.Lock()
|
|
|
+ if len(b.upEps) == 0 {
|
|
|
+ // notify waiting Get()s and pin first connected address
|
|
|
+ close(b.upc)
|
|
|
+ b.pinAddr = addr.Addr
|
|
|
+ }
|
|
|
+ b.upEps[addr.Addr] = struct{}{}
|
|
|
+ b.mu.Unlock()
|
|
|
+ // notify client that a connection is up
|
|
|
+ b.readyOnce.Do(func() { close(b.readyc) })
|
|
|
+ return func(err error) {
|
|
|
+ b.mu.Lock()
|
|
|
+ delete(b.upEps, addr.Addr)
|
|
|
+ if len(b.upEps) == 0 && b.pinAddr != "" {
|
|
|
+ b.upc = make(chan struct{})
|
|
|
+ } else if b.pinAddr == addr.Addr {
|
|
|
+ // choose new random up endpoint
|
|
|
+ for k := range b.upEps {
|
|
|
+ b.pinAddr = k
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ b.mu.Unlock()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-func (b *simpleBalancer) Start(target string) error { return nil }
|
|
|
-func (b *simpleBalancer) Up(addr grpc.Address) func(error) { return func(error) {} }
|
|
|
func (b *simpleBalancer) Get(ctx context.Context, opts grpc.BalancerGetOptions) (grpc.Address, func(), error) {
|
|
|
- v := atomic.AddUint32(&b.numGets, 1)
|
|
|
- ep := b.eps[v%uint32(len(b.eps))]
|
|
|
- return grpc.Address{Addr: getHost(ep)}, func() {}, nil
|
|
|
+ var addr string
|
|
|
+ for {
|
|
|
+ b.mu.RLock()
|
|
|
+ ch := b.upc
|
|
|
+ b.mu.RUnlock()
|
|
|
+ select {
|
|
|
+ case <-ch:
|
|
|
+ case <-ctx.Done():
|
|
|
+ return grpc.Address{Addr: ""}, nil, ctx.Err()
|
|
|
+ }
|
|
|
+ b.mu.RLock()
|
|
|
+ addr = b.pinAddr
|
|
|
+ upEps := len(b.upEps)
|
|
|
+ b.mu.RUnlock()
|
|
|
+ if addr == "" {
|
|
|
+ return grpc.Address{Addr: ""}, nil, grpc.ErrClientConnClosing
|
|
|
+ }
|
|
|
+ if upEps > 0 {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return grpc.Address{Addr: addr}, func() {}, nil
|
|
|
}
|
|
|
-func (b *simpleBalancer) Notify() <-chan []grpc.Address { return b.ch }
|
|
|
+
|
|
|
+func (b *simpleBalancer) Notify() <-chan []grpc.Address { return b.notifyCh }
|
|
|
+
|
|
|
func (b *simpleBalancer) Close() error {
|
|
|
- close(b.ch)
|
|
|
+ b.mu.Lock()
|
|
|
+ close(b.notifyCh)
|
|
|
+ // terminate all waiting Get()s
|
|
|
+ b.pinAddr = ""
|
|
|
+ if len(b.upEps) == 0 {
|
|
|
+ close(b.upc)
|
|
|
+ }
|
|
|
+ b.mu.Unlock()
|
|
|
return nil
|
|
|
}
|
|
|
|