diff --git a/clientconn.go b/clientconn.go index d74b8bf02c61..2299ed99f353 100644 --- a/clientconn.go +++ b/clientconn.go @@ -294,6 +294,13 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * s := cc.GetState() if s == connectivity.Ready { break + } else if cc.dopts.copts.FailOnNonTempDialError && s == connectivity.TransientFailure { + if err = cc.blockingpicker.connectionError(); err != nil { + terr, ok := err.(interface{ Temporary() bool }) + if ok && !terr.Temporary() { + return nil, err + } + } } if !cc.WaitForStateChange(ctx, s) { // ctx got timeout or canceled. diff --git a/clientconn_test.go b/clientconn_test.go index f2614dec51bf..f1fd368cd846 100644 --- a/clientconn_test.go +++ b/clientconn_test.go @@ -31,6 +31,7 @@ import ( "google.golang.org/grpc/credentials" "google.golang.org/grpc/internal/backoff" "google.golang.org/grpc/internal/leakcheck" + "google.golang.org/grpc/internal/transport" "google.golang.org/grpc/keepalive" "google.golang.org/grpc/naming" "google.golang.org/grpc/resolver" @@ -443,6 +444,25 @@ func TestDialContextCancel(t *testing.T) { } } +type failFastError struct{} + +func (failFastError) Error() string { return "failfast" } +func (failFastError) Temporary() bool { return false } + +func TestDialContextFailFast(t *testing.T) { + defer leakcheck.Check(t) + ctx := context.Background() + failErr := failFastError{} + dialer := func(string, time.Duration) (net.Conn, error) { + return nil, failErr + } + + _, err := DialContext(ctx, "Non-Existent.Server:80", WithBlock(), WithInsecure(), WithDialer(dialer), FailOnNonTempDialError(true)) + if terr, ok := err.(transport.ConnectionError); !ok || terr.Origin() != failErr { + t.Fatalf("DialContext() = _, %v, want _, %v", err, failErr) + } +} + // blockingBalancer mimics the behavior of balancers whose initialization takes a long time. // In this test, reading from blockingBalancer.Notify() blocks forever. type blockingBalancer struct {