{"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/03255a9237b6eb32710f6bc4f2de9a675b99fe36/balancer/base/balancer.go#L140-L176","documentation":"Produced by baseBalancer.mergeErrors when TransientFailure has BOTH a connection error and a resolver error. This combined form is the most diagnostic: it tells you both layers are failing (e.g. the resolver returned a partial/bad update AND the existing SubConns can't connect). Both underlying errors are concatenated so you can triage in one message.","triggerScenarios":"The balancer is in TransientFailure, connErr != nil and resolverErr != nil simultaneously. mergeErrors reaches the final return line.","commonSituations":"Resolver returns an error (DNS hiccup, xDS NACK) at the same time as existing backends are unreachable; a rolling deploy where the resolver hasn't refreshed and the old endpoints are dying; misconfigured custom resolver returning errors while TLS is also broken.","solutions":["Read BOTH wrapped values: the connection error first, the resolver error second.","Fix whichever is the root cause (often the resolver error clears first once discovery heals).","Capture a channelz snapshot to see per-SubConn state and the resolver's last error.","Apply a client RetryPolicy so the RPC survives the transient dual failure."],"exampleFix":"// Diagnose from the message itself, e.g.:\n// 'last connection error: dial tcp: connect: connection refused; last resolver error: produced zero addresses'\n// -> backends down AND resolver returning nothing. Fix service discovery first, then backend health.","handlingStrategy":"retry","validationCode":"// Combine resolver + dial pre-flight checks from errors 92 and 93.\nfunc preflight(target, dialAddr string) error {\n    if err := checkResolves(target); err != nil { return err }\n    return checkDial(dialAddr, 2*time.Second)\n}","typeGuard":null,"tryCatchPattern":"// Both layers failing usually means a broader outage; retry with backoff and\n// surface a clear error to the caller after exhausting retries.\npolicy := retryPolicy(codes.Unavailable, 5, 1*time.Second)\nreturn withRetry(ctx, policy, func() error { return stub.Do(ctx, req) })","preventionTips":["Monitor resolver AND backend health independently to localize the fault.","Use channelz snapshots during dual failures to capture both errors.","Configure RetryPolicy and graceful shutdown windows during deploys.","Keep service discovery and backend health checks tightly coupled."],"tags":["balancer","base","transient-failure","connection","resolver","runtime","network"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}