transport: robustly detect temporary errors

A bit paranoid, but should help mitigate more issues like #859.
This commit is contained in:
Tamir Duberstein
2016-08-25 14:43:58 -04:00
parent e1b61502bc
commit 0df4503b9a

View File

@ -114,14 +114,42 @@ func dial(fn func(context.Context, string) (net.Conn, error), ctx context.Contex
return dialContext(ctx, "tcp", addr) return dialContext(ctx, "tcp", addr)
} }
func isTemporary(err error) bool {
switch err {
case io.EOF:
// Connection closures may be resolved upon retry, and are thus
// treated as temporary.
return true
case context.DeadlineExceeded:
// In Go 1.7, context.DeadlineExceeded implements Timeout(), and this
// special case is not needed. Until then, we need to keep this
// clause.
return true
}
switch err := err.(type) {
case interface {
Temporary() bool
}:
return err.Temporary()
case interface {
Timeout() bool
}:
// Timeouts may be resolved upon retry, and are thus treated as
// temporary.
return err.Timeout()
}
return false
}
// newHTTP2Client constructs a connected ClientTransport to addr based on HTTP2 // newHTTP2Client constructs a connected ClientTransport to addr based on HTTP2
// and starts to receive messages on it. Non-nil error returns if construction // and starts to receive messages on it. Non-nil error returns if construction
// fails. // fails.
func newHTTP2Client(ctx context.Context, addr string, opts ConnectOptions) (_ ClientTransport, err error) { func newHTTP2Client(ctx context.Context, addr string, opts ConnectOptions) (_ ClientTransport, err error) {
scheme := "http" scheme := "http"
conn, connErr := dial(opts.Dialer, ctx, addr) conn, err := dial(opts.Dialer, ctx, addr)
if connErr != nil { if err != nil {
return nil, ConnectionErrorf(true, connErr, "transport: %v", connErr) return nil, ConnectionErrorf(true, err, "transport: %v", err)
} }
// Any further errors will close the underlying connection // Any further errors will close the underlying connection
defer func(conn net.Conn) { defer func(conn net.Conn) {
@ -132,17 +160,13 @@ func newHTTP2Client(ctx context.Context, addr string, opts ConnectOptions) (_ Cl
var authInfo credentials.AuthInfo var authInfo credentials.AuthInfo
if creds := opts.TransportCredentials; creds != nil { if creds := opts.TransportCredentials; creds != nil {
scheme = "https" scheme = "https"
conn, authInfo, connErr = creds.ClientHandshake(ctx, addr, conn) conn, authInfo, err = creds.ClientHandshake(ctx, addr, conn)
if err != nil {
// Credentials handshake errors are typically considered permanent
// to avoid retrying on e.g. bad certificates.
temp := isTemporary(err)
return nil, ConnectionErrorf(temp, err, "transport: %v", err)
} }
if connErr != nil {
// Credentials handshake error is not a temporary error (unless the error
// was the connection closing or deadline exceeded).
var temp bool
switch connErr {
case io.EOF, context.DeadlineExceeded:
temp = true
}
return nil, ConnectionErrorf(temp, connErr, "transport: %v", connErr)
} }
ua := primaryUA ua := primaryUA
if opts.UserAgent != "" { if opts.UserAgent != "" {