{"record":{"id":"b83d3177da1e6052","repo":"grpc/grpc-go","slug":"last-connection-error-v-last-resolver-error-v","errorCode":null,"errorMessage":"last connection error: %v; last resolver error: %v","messagePattern":"last connection error: (.+?); last resolver error: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"balancer/base/balancer.go","lineNumber":158,"sourceCode":"\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 {\n\t\t\treadySCs[sc] = SubConnInfo{Address: addr}\n\t\t}","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/balancer/base/balancer.go#L140-L176","documentation":"Produced by mergeErrors() (balancer/base/balancer.go:158) when the balancer is in TransientFailure and BOTH a connection error and a resolver error are present (connErr != nil && resolverErr != nil). resolverErr is set when the resolver reports an error or produces zero addresses; connErr is set when a SubConn enters TransientFailure. The merged message surfaces both failure dimensions so you can distinguish a pure connectivity problem from a resolution problem.","triggerScenarios":"The resolver reported an error via ResolverError() or produced zero addresses (set internally at balancer.go:138), AND at least one SubConn that was created earlier entered TransientFailure. The aggregated balancer state is TransientFailure, so regeneratePicker() calls mergeErrors() which takes the both-errors branch at line 158.","commonSituations":"DNS server is unreachable while backends are also down; service config targets a nonexistent service name; xDS control plane returns an empty/bad endpoint list while connections to stale endpoints fail; a network partition affects both name resolution and backend reachability.","solutions":["First fix the resolver error (the second %v) — the resolver must return at least one valid address.","Then address the connection error (the first %v) using the same steps as a pure connection failure.","If using DNS, confirm the DNS server is reachable and the service name resolves.","If using xDS, verify the control plane (EDS/CDS resources) is healthy and returns endpoints."],"exampleFix":"// before: resolver and backends both broken\n// target does not resolve AND old backends are unreachable\nconn, _ := grpc.NewClient(\"dns:///nonexistent-svc:50051\")\n\n// after: correct service name that the resolver can resolve to live backends\nconn, _ := grpc.NewClient(\"dns:///real-svc.default.svc.cluster.local:50051\")","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Both resolver and connection failed. Retry after re-resolution.\nfor {\n    resp, err := c.Call(ctx, in, grpc.WaitForReady(true))\n    if err == nil { return resp, nil }\n    if status.Code(err) != codes.Unavailable { return nil, err }\n    select {\n    case <-time.After(2 * time.Second):\n    case <-ctx.Done(): return nil, ctx.Err()\n    }\n}","preventionTips":["Run DNS / xDS resolver health checks alongside backend health checks.","Keep the resolver and backends in independent failure domains so one outage does not cascade.","Log both the resolver and connection error portions to triage which layer failed first."],"tags":["grpc","balancer","connection","resolver","transient-failure","network","base"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}