{"record":{"id":"1abfb16b7e160a1b","repo":"grpc/grpc-go","slug":"last-connection-error-v","errorCode":null,"errorMessage":"last connection error: %v","messagePattern":"last connection error: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"balancer/base/balancer.go","lineNumber":156,"sourceCode":"\t\tb.ResolverError(errors.New(\"produced zero addresses\"))\n\t\treturn balancer.ErrBadResolverState\n\t}\n\n\tb.regeneratePicker()\n\tb.cc.UpdateState(balancer.State{ConnectivityState: b.state, Picker: b.picker})\n\treturn nil\n}\n\n// mergeErrors builds an error from the last connection error and the last\n// resolver error.  Must only be called if b.state is TransientFailure.\nfunc (b *baseBalancer) mergeErrors() error {\n\t// connErr must always be non-nil unless there are no SubConns, in which\n\t// case resolverErr must be non-nil.\n\tif b.connErr == nil {\n\t\treturn fmt.Errorf(\"last resolver error: %v\", b.resolverErr)\n\t}\n\tif b.resolverErr == nil {\n\t\treturn fmt.Errorf(\"last connection error: %v\", b.connErr)\n\t}\n\treturn fmt.Errorf(\"last connection error: %v; last resolver error: %v\", b.connErr, b.resolverErr)\n}\n\n// regeneratePicker takes a snapshot of the balancer, and generates a picker\n// from it. The picker is\n//   - errPicker if the balancer is in TransientFailure,\n//   - built by the pickerBuilder with all READY SubConns otherwise.\nfunc (b *baseBalancer) regeneratePicker() {\n\tif b.state == connectivity.TransientFailure {\n\t\tb.picker = NewErrPicker(b.mergeErrors())\n\t\treturn\n\t}\n\treadySCs := make(map[balancer.SubConn]SubConnInfo)\n\n\t// Filter out all ready SCs from full subConn map.\n\tfor addr, sc := range b.subConns.All() {\n\t\tif st, ok := b.scStates[sc]; ok && st == connectivity.Ready {","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/balancer/base/balancer.go#L138-L174","documentation":"Produced by baseBalancer.mergeErrors when the balancer is in TransientFailure, a SubConn connection error exists (connErr != nil) but there is no resolver error. This is the classic 'all backends unreachable' case: the resolver gave good addresses but every TCP/TLS handshake failed. The error is wrapped in the picker and returned as the RPC's Unavailable status.","triggerScenarios":"All SubConns under a base-balancer policy (round_robin, weighted_round_robin, etc.) have entered TransientFailure with a non-nil connection error, while the resolver itself succeeded. mergeErrors hits the `b.resolverErr == nil` branch.","commonSituations":"Backends are down or refusing connections; TLS certificate validation failure on every backend; firewall/security group blocking the gRPC port; wrong port in the resolved address; server crashed and endpoints haven't been re-registered.","solutions":["Read the wrapped %v — it is the underlying connection error (e.g. dial tcp: connection refused, x509 cert error).","Verify network reachability to the backend address/port from the client host.","If TLS-related, check cert validity, SANs, and the client's credentials config.","Restart/health-check the backends and confirm service discovery reflects them as healthy."],"exampleFix":"// before: TLS handshake fails on all backends -> 'last connection error: ... x509: certificate signed by unknown authority'\nconn, _ := grpc.NewClient(addr, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})))   // no CA\n\n// after:\ncreds := credentials.NewClientTLSFromCert(caPool, \"\")\nconn, _ := grpc.NewClient(addr, grpc.WithTransportCredentials(creds))","handlingStrategy":"retry","validationCode":"// Pre-flight: dial the backend address to catch connection-level failures\n// before the channel's SubConns all enter TransientFailure.\nfunc checkDial(addr string, timeout time.Duration) error {\n    conn, err := net.DialTimeout(\"tcp\", addr, timeout)\n    if err != nil { return err }\n    conn.Close()\n    return nil\n}","typeGuard":null,"tryCatchPattern":"// Treat as transient; retry with backoff for idempotent RPCs.\nvar lastErr error\nfor i := 0; i < maxRetries; i++ {\n    lastErr = stub.Do(ctx, req)\n    if lastErr == nil { return nil }\n    if status.Code(lastErr) != codes.Unavailable { return lastErr }\n    select { case <-time.After(backoffFor(i)): case <-ctx.Done(): return ctx.Err() }\n}\nreturn lastErr","preventionTips":["Validate TLS CA/certs before deploying the client.","Health-check backends and gate on readiness probes.","Configure keepalive and RetryPolicy to survive transient outages.","Use channelz to watch per-SubConn connection errors."],"tags":["balancer","base","transient-failure","connection","runtime","network","tls"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}