{"record":{"id":"136bb3b8fd1e382e","repo":"grpc/grpc-go","slug":"v-v","errorCode":null,"errorMessage":"%v: %v","messagePattern":"%v: %v","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"clientconn.go","lineNumber":326,"sourceCode":"\tif !cc.dopts.block {\n\t\treturn cc, nil\n\t}\n\n\tif cc.dopts.timeout > 0 {\n\t\tvar cancel context.CancelFunc\n\t\tctx, cancel = context.WithTimeout(ctx, cc.dopts.timeout)\n\t\tdefer cancel()\n\t}\n\tdefer func() {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tswitch {\n\t\t\tcase ctx.Err() == err:\n\t\t\t\tconn = nil\n\t\t\tcase err == nil || !cc.dopts.returnLastError:\n\t\t\t\tconn, err = nil, ctx.Err()\n\t\t\tdefault:\n\t\t\t\tconn, err = nil, fmt.Errorf(\"%v: %v\", ctx.Err(), err)\n\t\t\t}\n\t\tdefault:\n\t\t}\n\t}()\n\n\t// A blocking dial blocks until the clientConn is ready.\n\tfor {\n\t\ts := cc.GetState()\n\t\tif s == connectivity.Idle {\n\t\t\tcc.Connect()\n\t\t}\n\t\tif s == connectivity.Ready {\n\t\t\treturn cc, nil\n\t\t} else if cc.dopts.copts.FailOnNonTempDialError && s == connectivity.TransientFailure {\n\t\t\tif err = cc.connectionError(); err != nil {\n\t\t\t\tterr, ok := err.(interface {\n\t\t\t\t\tTemporary() bool\n\t\t\t\t})","sourceCodeStart":308,"sourceCodeEnd":344,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/clientconn.go#L308-L344","documentation":"In the blocking DialContext loop (clientconn.go:317-330), a deferred handler fires when the dial context expires or is cancelled. If there is BOTH a context error and a connection error (and returnLastError is not the chosen branch), it returns fmt.Errorf(\"%v: %v\", ctx.Err(), err), combining e.g. a deadline-exceeded with the underlying connection failure.","triggerScenarios":"Using grpc.DialContext with WithBlock(true) and a context that times out while the connection is also failing. The combined message surfaces when the context, not the transport, is the proximate cause.","commonSituations":"A short dial timeout against an unreachable backend; DNS resolution slower than the context deadline; the server rejecting TLS while the context expires simultaneously.","solutions":["Increase the dial context timeout or remove WithBlock if you don't need a synchronous ready.","Address the underlying connection error shown after the colon (TLS, DNS, refused connection).","Switch to grpc.NewClient which is non-blocking and reports failures per-RPC instead of at dial time."],"exampleFix":"// before\nctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)\nconn, err := grpc.DialContext(ctx, target, grpc.WithBlock())\n// after\nctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\nconn, err := grpc.DialContext(ctx, target, grpc.WithBlock())","handlingStrategy":"try-catch","validationCode":"// Size the context timeout to realistic connection latency before a blocking dial.\nfunc dialTimeout(target string) time.Duration {\n    if strings.HasPrefix(target, \"xds:///\") { return 60 * time.Second }\n    return 10 * time.Second\n}","typeGuard":null,"tryCatchPattern":"conn, err := grpc.DialContext(ctx, target, grpc.WithBlock())\nif err != nil {\n    // err may combine ctx.Err() and connection error; inspect both\n    log.Printf(\"dial failed: %v (ctx: %v)\", err, ctx.Err())\n    return nil, err\n}","preventionTips":["Avoid WithBlock for long-lived channels; use NewClient and handle errors per-RPC.","Set timeouts based on measured worst-case dial latency, not arbitrary small values.","Separate deadline-exceeded from transport errors when logging."],"tags":["go","grpc","clientconn","timeout","dial"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}