clientv3: avoid reusing closed connection in KV

This commit is contained in:
Anthony Romano 2016-05-18 14:46:17 -07:00
parent 90498b3756
commit 782a8802c0
4 changed files with 97 additions and 54 deletions

View File

@ -277,6 +277,7 @@ func (c *Client) retryConnection(err error) (newConn *grpc.ClientConn, dialErr e
// wait so grpc doesn't leak sleeping goroutines // wait so grpc doesn't leak sleeping goroutines
c.conn.WaitForStateChange(context.Background(), st) c.conn.WaitForStateChange(context.Background(), st)
} }
c.conn = nil
} }
if c.cancel == nil { if c.cancel == nil {
// client has called Close() so don't try to dial out // client has called Close() so don't try to dial out

View File

@ -105,7 +105,12 @@ func (kv *kv) Delete(ctx context.Context, key string, opts ...OpOption) (*Delete
} }
func (kv *kv) Compact(ctx context.Context, rev int64) error { func (kv *kv) Compact(ctx context.Context, rev int64) error {
_, err := kv.getRemote().Compact(ctx, &pb.CompactionRequest{Revision: rev}) remote, err := kv.getRemote(ctx)
if err != nil {
return rpctypes.Error(err)
}
defer kv.rc.release()
_, err = remote.Compact(ctx, &pb.CompactionRequest{Revision: rev})
if err == nil { if err == nil {
return nil return nil
} }
@ -125,8 +130,31 @@ func (kv *kv) Txn(ctx context.Context) Txn {
func (kv *kv) Do(ctx context.Context, op Op) (OpResponse, error) { func (kv *kv) Do(ctx context.Context, op Op) (OpResponse, error) {
for { for {
var err error resp, err := kv.do(ctx, op)
remote := kv.getRemote() if err == nil {
return resp, nil
}
if isHaltErr(ctx, err) {
return resp, rpctypes.Error(err)
}
// do not retry on modifications
if op.isWrite() {
kv.rc.reconnect(err)
return resp, rpctypes.Error(err)
}
if nerr := kv.rc.reconnectWait(ctx, err); nerr != nil {
return resp, rpctypes.Error(nerr)
}
}
}
func (kv *kv) do(ctx context.Context, op Op) (OpResponse, error) {
remote, err := kv.getRemote(ctx)
if err != nil {
return OpResponse{}, err
}
defer kv.rc.release()
switch op.t { switch op.t {
// TODO: handle other ops // TODO: handle other ops
case tRange: case tRange:
@ -158,25 +186,12 @@ func (kv *kv) Do(ctx context.Context, op Op) (OpResponse, error) {
default: default:
panic("Unknown op") panic("Unknown op")
} }
return OpResponse{}, err
if isHaltErr(ctx, err) {
return OpResponse{}, rpctypes.Error(err)
}
// do not retry on modifications
if op.isWrite() {
kv.rc.reconnect(err)
return OpResponse{}, rpctypes.Error(err)
}
if nerr := kv.rc.reconnectWait(ctx, err); nerr != nil {
return OpResponse{}, nerr
}
}
} }
func (kv *kv) getRemote() pb.KVClient { func (kv *kv) getRemote(ctx context.Context) (pb.KVClient, error) {
kv.rc.mu.Lock() if err := kv.rc.acquire(ctx); err != nil {
defer kv.rc.mu.Unlock() return nil, err
return kv.remote }
return kv.remote, nil
} }

View File

@ -77,3 +77,22 @@ func (r *remoteClient) tryUpdate() bool {
r.updateConn(activeConn) r.updateConn(activeConn)
return true return true
} }
func (r *remoteClient) acquire(ctx context.Context) error {
for {
r.client.mu.RLock()
c := r.client.conn
r.mu.Lock()
match := r.conn == c
r.mu.Unlock()
if match {
return nil
}
r.client.mu.RUnlock()
if err := r.reconnectWait(ctx, nil); err != nil {
return err
}
}
}
func (r *remoteClient) release() { r.client.mu.RUnlock() }

View File

@ -137,27 +137,35 @@ func (txn *txn) Else(ops ...Op) Txn {
func (txn *txn) Commit() (*TxnResponse, error) { func (txn *txn) Commit() (*TxnResponse, error) {
txn.mu.Lock() txn.mu.Lock()
defer txn.mu.Unlock() defer txn.mu.Unlock()
kv := txn.kv
for { for {
r := &pb.TxnRequest{Compare: txn.cmps, Success: txn.sus, Failure: txn.fas} resp, err := txn.commit()
resp, err := kv.getRemote().Txn(txn.ctx, r)
if err == nil { if err == nil {
return (*TxnResponse)(resp), nil return resp, err
} }
if isHaltErr(txn.ctx, err) { if isHaltErr(txn.ctx, err) {
return nil, rpctypes.Error(err) return nil, rpctypes.Error(err)
} }
if txn.isWrite { if txn.isWrite {
kv.rc.reconnect(err) txn.kv.rc.reconnect(err)
return nil, rpctypes.Error(err) return nil, rpctypes.Error(err)
} }
if nerr := txn.kv.rc.reconnectWait(txn.ctx, err); nerr != nil {
if nerr := kv.rc.reconnectWait(txn.ctx, err); nerr != nil {
return nil, nerr return nil, nerr
} }
} }
} }
func (txn *txn) commit() (*TxnResponse, error) {
rem, rerr := txn.kv.getRemote(txn.ctx)
if rerr != nil {
return nil, rerr
}
defer txn.kv.rc.release()
r := &pb.TxnRequest{Compare: txn.cmps, Success: txn.sus, Failure: txn.fas}
resp, err := rem.Txn(txn.ctx, r)
if err != nil {
return nil, err
}
return (*TxnResponse)(resp), nil
}