github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/grpcutil/grpc_util.go (about) 1 // Copyright 2014 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package grpcutil 12 13 import ( 14 "context" 15 "fmt" 16 "io" 17 "strings" 18 19 "github.com/cockroachdb/cockroach/pkg/util/netutil" 20 "github.com/cockroachdb/errors" 21 "google.golang.org/grpc" 22 "google.golang.org/grpc/codes" 23 "google.golang.org/grpc/connectivity" 24 "google.golang.org/grpc/status" 25 ) 26 27 // ErrCannotReuseClientConn is returned when a failed connection is 28 // being reused. We require that new connections be created with 29 // pkg/rpc.GRPCDial instead. 30 var ErrCannotReuseClientConn = errors.New("cannot reuse client connection") 31 32 type localRequestKey struct{} 33 34 // NewLocalRequestContext returns a Context that can be used for local (in-process) requests. 35 func NewLocalRequestContext(ctx context.Context) context.Context { 36 return context.WithValue(ctx, localRequestKey{}, struct{}{}) 37 } 38 39 // IsLocalRequestContext returns true if this context is marked for local (in-process) use. 40 func IsLocalRequestContext(ctx context.Context) bool { 41 return ctx.Value(localRequestKey{}) != nil 42 } 43 44 // IsTimeout returns true if err's Cause is a gRPC timeout, or the request 45 // was canceled by a context timeout. 46 func IsTimeout(err error) bool { 47 if errors.Is(err, context.DeadlineExceeded) { 48 return true 49 } 50 err = errors.Cause(err) 51 if s, ok := status.FromError(err); ok { 52 return s.Code() == codes.DeadlineExceeded 53 } 54 return false 55 } 56 57 // IsClosedConnection returns true if err's Cause is an error produced by gRPC 58 // on closed connections. 59 func IsClosedConnection(err error) bool { 60 if errors.Is(err, ErrCannotReuseClientConn) { 61 return true 62 } 63 err = errors.Cause(err) 64 if s, ok := status.FromError(err); ok { 65 if s.Code() == codes.Canceled || 66 s.Code() == codes.Unavailable { 67 return true 68 } 69 } 70 if errors.Is(err, context.Canceled) || 71 strings.Contains(err.Error(), "is closing") || 72 strings.Contains(err.Error(), "tls: use of closed connection") || 73 strings.Contains(err.Error(), "use of closed network connection") || 74 strings.Contains(err.Error(), io.ErrClosedPipe.Error()) || 75 strings.Contains(err.Error(), io.EOF.Error()) || 76 strings.Contains(err.Error(), "node unavailable") { 77 return true 78 } 79 return netutil.IsClosedConnection(err) 80 } 81 82 // RequestDidNotStart returns true if the given error from gRPC 83 // means that the request definitely could not have started on the 84 // remote server. 85 // 86 // This method currently depends on implementation details, matching 87 // on the text of an error message that is known to only be used 88 // in this case in the version of gRPC that we use today. We will 89 // need to watch for changes here in future versions of gRPC. 90 // TODO(bdarnell): Replace this with a cleaner mechanism when/if 91 // https://github.com/grpc/grpc-go/issues/1443 is resolved. 92 func RequestDidNotStart(err error) bool { 93 if errors.HasType(err, connectionNotReadyError{}) || 94 errors.HasType(err, (*netutil.InitialHeartbeatFailedError)(nil)) { 95 return true 96 } 97 s, ok := status.FromError(errors.Cause(err)) 98 if !ok { 99 // This is a non-gRPC error; assume nothing. 100 return false 101 } 102 // TODO(bdarnell): In gRPC 1.7, we have no good way to distinguish 103 // ambiguous from unambiguous failures, so we must assume all gRPC 104 // errors are ambiguous. 105 // https://github.com/cockroachdb/cockroach/issues/19708#issuecomment-343891640 106 if false && s.Code() == codes.Unavailable && s.Message() == "grpc: the connection is unavailable" { 107 return true 108 } 109 return false 110 } 111 112 // ConnectionReady returns nil if the given connection is ready to 113 // send a request, or an error (which will pass RequestDidNotStart) if 114 // not. 115 // 116 // This is a workaround for the fact that gRPC 1.7 fails to 117 // distinguish between ambiguous and unambiguous errors. 118 // 119 // This is designed for use with connections prepared by 120 // pkg/rpc.Connection.Connect (which performs an initial heartbeat and 121 // thereby ensures that we will never see a connection in the 122 // first-time Connecting state). 123 func ConnectionReady(conn *grpc.ClientConn) error { 124 if s := conn.GetState(); s == connectivity.TransientFailure { 125 return connectionNotReadyError{s} 126 } 127 return nil 128 } 129 130 type connectionNotReadyError struct { 131 state connectivity.State 132 } 133 134 func (e connectionNotReadyError) Error() string { 135 return fmt.Sprintf("connection not ready: %s", e.state) 136 }