grpc-go/clientconn.go

264 строки
7.8 KiB
Go
Исходник Обычный вид История

2015-02-06 04:14:05 +03:00
/*
*
* Copyright 2014, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package grpc
2015-02-06 04:14:05 +03:00
import (
"errors"
2015-02-26 03:28:20 +03:00
"log"
2015-02-06 04:14:05 +03:00
"sync"
"time"
"golang.org/x/net/context"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/transport"
2015-02-06 04:14:05 +03:00
)
var (
// ErrUnspecTarget indicates that the target address is unspecified.
ErrUnspecTarget = errors.New("grpc: target is unspecified")
2015-02-23 22:51:15 +03:00
// ErrClientConnClosing indicates that the operation is illegal because
// the session is closing.
ErrClientConnClosing = errors.New("grpc: the client connection is closing")
2015-03-04 04:08:39 +03:00
// ErrClientConnTimeout indicates that the connection could not be
// established or re-established within the specified timeout.
2015-03-04 04:08:39 +03:00
ErrClientConnTimeout = errors.New("grpc: timed out trying to connect")
)
2015-03-04 04:08:39 +03:00
// DialOption configures how we set up the connection.
type DialOption func(*transport.DialOptions)
2015-02-06 04:14:05 +03:00
// WithTransportCredentials returns a DialOption which configures a
// connection level security credentials (e.g., TLS/SSL).
func WithTransportCredentials(creds credentials.TransportAuthenticator) DialOption {
2015-03-04 04:08:39 +03:00
return func(o *transport.DialOptions) {
o.AuthOptions = append(o.AuthOptions, creds)
2015-02-06 04:14:05 +03:00
}
}
// WithPerRPCCredentials returns a DialOption which sets
// credentials which will place auth state on each outbound RPC.
func WithPerRPCCredentials(creds credentials.Credentials) DialOption {
2015-03-04 04:08:39 +03:00
return func(o *transport.DialOptions) {
o.AuthOptions = append(o.AuthOptions, creds)
}
}
2015-03-04 22:15:10 +03:00
// WithTimeout returns a DialOption that configures a timeout for dialing a client connection.
2015-03-04 04:08:39 +03:00
func WithTimeout(d time.Duration) DialOption {
return func(o *transport.DialOptions) {
o.Timeout = d
2015-02-06 04:14:05 +03:00
}
}
// Dial creates a client connection the given target.
// TODO(zhaoq): Have an option to make Dial return immediately without waiting
// for connection to complete.
func Dial(target string, opts ...DialOption) (*ClientConn, error) {
if target == "" {
return nil, ErrUnspecTarget
2015-02-06 04:14:05 +03:00
}
cc := &ClientConn{
target: target,
}
for _, opt := range opts {
opt(&cc.dopts)
}
2015-02-23 05:00:33 +03:00
if err := cc.resetTransport(false); err != nil {
2015-02-06 04:14:05 +03:00
return nil, err
}
cc.shutdownChan = make(chan struct{})
// Start to monitor the error status of transport.
go cc.transportMonitor()
return cc, nil
}
// ClientConn represents a client connection to an RPC service.
type ClientConn struct {
target string
2015-03-04 04:08:39 +03:00
dopts transport.DialOptions
2015-02-06 04:14:05 +03:00
shutdownChan chan struct{}
mu sync.Mutex
// ready is closed and becomes nil when a new transport is up or failed
// due to timeout.
2015-02-06 04:14:05 +03:00
ready chan struct{}
// Indicates the ClientConn is under destruction.
closing bool
// Every time a new transport is created, this is incremented by 1. Used
// to avoid trying to recreate a transport while the new one is already
// under construction.
transportSeq int
transport transport.ClientTransport
}
func (cc *ClientConn) resetTransport(closeTransport bool) error {
var retries int
2015-03-04 04:08:39 +03:00
start := time.Now()
2015-02-06 04:14:05 +03:00
for {
cc.mu.Lock()
t := cc.transport
ts := cc.transportSeq
// Avoid wait() picking up a dying transport unnecessarily.
cc.transportSeq = 0
if cc.closing {
cc.mu.Unlock()
2015-02-23 22:51:15 +03:00
return ErrClientConnClosing
2015-02-06 04:14:05 +03:00
}
cc.mu.Unlock()
if closeTransport {
t.Close()
}
2015-03-04 04:08:39 +03:00
// Adjust timeout for the current try.
dopts := cc.dopts
2015-03-05 00:23:39 +03:00
if dopts.Timeout < 0 {
cc.Close()
return ErrClientConnTimeout
}
if dopts.Timeout > 0 {
dopts.Timeout -= time.Since(start)
if dopts.Timeout <= 0 {
cc.Close()
2015-03-04 04:08:39 +03:00
return ErrClientConnTimeout
}
}
2015-03-04 22:15:10 +03:00
newTransport, err := transport.NewClientTransport(cc.target, &dopts)
2015-02-06 04:14:05 +03:00
if err != nil {
sleepTime := backoff(retries)
// Fail early before falling into sleep.
2015-03-13 10:16:18 +03:00
if cc.dopts.Timeout > 0 && cc.dopts.Timeout < sleepTime+time.Since(start) {
cc.Close()
2015-03-04 04:08:39 +03:00
return ErrClientConnTimeout
}
2015-02-06 04:14:05 +03:00
closeTransport = false
time.Sleep(sleepTime)
2015-02-06 04:14:05 +03:00
retries++
2015-03-04 04:08:39 +03:00
// TODO(zhaoq): Record the error with glog.V.
2015-02-26 03:28:20 +03:00
log.Printf("grpc: ClientConn.resetTransport failed to create client transport: %v; Reconnecting to %q", err, cc.target)
2015-02-06 04:14:05 +03:00
continue
}
cc.mu.Lock()
2015-03-05 20:45:50 +03:00
if cc.closing {
// cc.Close() has been invoked.
cc.mu.Unlock()
newTransport.Close()
return ErrClientConnClosing
}
2015-02-06 04:14:05 +03:00
cc.transport = newTransport
cc.transportSeq = ts + 1
if cc.ready != nil {
close(cc.ready)
cc.ready = nil
}
cc.mu.Unlock()
return nil
}
}
// Run in a goroutine to track the error in transport and create the
// new transport if an error happens. It returns when the channel is closing.
func (cc *ClientConn) transportMonitor() {
for {
select {
// shutdownChan is needed to detect the channel teardown when
// the ClientConn is idle (i.e., no RPC in flight).
case <-cc.shutdownChan:
return
case <-cc.transport.Error():
2015-02-23 05:00:33 +03:00
if err := cc.resetTransport(true); err != nil {
2015-02-06 04:14:05 +03:00
// The channel is closing.
// TODO(zhaoq): Record the error with glog.V.
2015-03-04 22:15:10 +03:00
log.Printf("grpc: ClientConn.transportMonitor exits due to: %v", err)
2015-02-06 04:14:05 +03:00
return
}
continue
}
}
}
// When wait returns, either the new transport is up or ClientConn is
// closing. Used to avoid working on a dying transport. It updates and
// returns the transport and its version when there is no error.
func (cc *ClientConn) wait(ctx context.Context, ts int) (transport.ClientTransport, int, error) {
for {
cc.mu.Lock()
switch {
case cc.closing:
cc.mu.Unlock()
2015-02-23 22:51:15 +03:00
return nil, 0, ErrClientConnClosing
2015-02-06 04:14:05 +03:00
case ts < cc.transportSeq:
// Worked on a dying transport. Try the new one immediately.
defer cc.mu.Unlock()
return cc.transport, cc.transportSeq, nil
default:
ready := cc.ready
if ready == nil {
ready = make(chan struct{})
cc.ready = ready
}
cc.mu.Unlock()
select {
case <-ctx.Done():
return nil, 0, transport.ContextErr(ctx.Err())
// Wait until the new transport is ready or failed.
2015-02-06 04:14:05 +03:00
case <-ready:
}
}
}
}
// Close starts to tear down the ClientConn. Returns ErrClientConnClosing if
// it has been closed (mostly due to dial time-out).
2015-02-06 04:14:05 +03:00
// TODO(zhaoq): Make this synchronous to avoid unbounded memory consumption in
// some edge cases (e.g., the caller opens and closes many ClientConn's in a
// tight loop.
func (cc *ClientConn) Close() error {
2015-02-06 04:14:05 +03:00
cc.mu.Lock()
defer cc.mu.Unlock()
if cc.closing {
return ErrClientConnClosing
2015-02-06 04:14:05 +03:00
}
cc.closing = true
2015-03-05 00:00:47 +03:00
if cc.ready != nil {
close(cc.ready)
cc.ready = nil
}
if cc.transport != nil {
cc.transport.Close()
}
if cc.shutdownChan != nil {
close(cc.shutdownChan)
}
return nil
2015-02-06 04:14:05 +03:00
}