{"record":{"id":"131ce36ba8643844","repo":"grpc/grpc-go","slug":"outlier-detection-error-switching-to-child-of-typ","errorCode":null,"errorMessage":"outlier detection: error switching to child of type %q: %v","messagePattern":"outlier detection: error switching to child of type %q: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/balancer/outlierdetection/balancer.go","lineNumber":307,"sourceCode":"\tif !ok {\n\t\tb.logger.Errorf(\"received config with unexpected type %T: %v\", s.BalancerConfig, s.BalancerConfig)\n\t\treturn balancer.ErrBadResolverState\n\t}\n\n\t// Reject whole config if child policy doesn't exist, don't persist it for\n\t// later.\n\tbb := balancer.Get(lbCfg.ChildPolicy.Name)\n\tif bb == nil {\n\t\treturn fmt.Errorf(\"outlier detection: child balancer %q not registered\", lbCfg.ChildPolicy.Name)\n\t}\n\n\t// It is safe to read b.cfg here without holding the mutex, as the only\n\t// write to b.cfg happens later in this function. This function is part of\n\t// the balancer.Balancer API, so it is guaranteed to be called in a\n\t// synchronous manner, so it cannot race with this read.\n\tif b.cfg == nil || b.cfg.ChildPolicy.Name != lbCfg.ChildPolicy.Name {\n\t\tif err := b.child.switchTo(bb); err != nil {\n\t\t\treturn fmt.Errorf(\"outlier detection: error switching to child of type %q: %v\", lbCfg.ChildPolicy.Name, err)\n\t\t}\n\t}\n\n\tb.mu.Lock()\n\t// Inhibit child picker updates until this UpdateClientConnState() call\n\t// completes. If needed, a picker update containing the no-op config bit\n\t// determined from this config and most recent state from the child will be\n\t// sent synchronously upward at the end of this UpdateClientConnState()\n\t// call.\n\tb.inhibitPickerUpdates = true\n\tb.updateUnconditionally = false\n\tb.cfg = lbCfg\n\n\tnewEndpoints := resolver.NewEndpointMap[bool]()\n\tfor _, ep := range s.ResolverState.Endpoints {\n\t\tnewEndpoints.Set(ep, true)\n\t\tif _, ok := b.endpoints.Get(ep); !ok {\n\t\t\tb.endpoints.Set(ep, newEndpointInfo())","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/balancer/outlierdetection/balancer.go#L289-L325","documentation":"Returned by outlier detection UpdateClientConnState (internal/xds/balancer/outlierdetection/balancer.go:306) when gracefulswitch.Balancer.SwitchTo fails while switching to the configured child policy. SwitchTo can fail if the child builder's Build returns nil; the error is wrapped with the child policy name.","triggerScenarios":"The child balancer's Build() returns nil (e.g. weighted_target's builder returns nil when its own dependency is missing) and gracefulswitch.SwitchTo surfaces an error, which UpdateClientConnState wraps.","commonSituations":"A child balancer's transitive dependency is missing or not registered; a child Build() defensively returns nil due to an internal invariant; concurrent close racing the switch.","solutions":["Check the underlying error (%v) — it usually names the missing dependency or builder","Ensure the child balancer and its dependencies are imported/registered","If it is a transient race during shutdown, verify the balancer is not being reconfigured after Close"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := bal.UpdateClientConnState(state); err != nil {\n    var se interface{ Unwrap() error }\n    if errors.As(err, &se) {\n        logger.Errorf(\"child switch failed; retaining prior child: %v\", se.Unwrap())\n    }\n    return err // gRPC core will retry with the next resolver update\n}","preventionTips":["Import child balancer packages and their dependencies","Avoid reconfiguring a balancer that has been Closed","Inspect the wrapped error for the missing builder/dependency"],"tags":["grpc","xds","outlier-detection","load-balancing","gracefulswitch","go"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}