{"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/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/balancer/outlierdetection/balancer.go#L289-L325","documentation":"When the child policy type changes (or first config arrives with a new name), outlier_detection calls gracefulswitch.SwitchTo via b.child.switchTo (balancer.go:305-308). If SwitchTo fails (typically the new builder's Build returns an error), the whole UpdateClientConnState fails. The previous child is unaffected because SwitchTo only swaps on success.","triggerScenarios":"b.child.switchTo(bb) returns a non-nil error from gracefulswitch.Balancer.SwitchTo, which propagates the underlying builder.Build error.","commonSituations":"Child balancer Build() fails (invalid build options, resource exhaustion, init errors); custom balancer whose Build returns an error; rare concurrent switch paths.","solutions":["Inspect the wrapped error for the underlying builder.Build failure reason.","Ensure the child balancer's Build can succeed in this environment (credentials, options, target).","If using a custom child, make Build robust or return a clearer error."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// UpdateClientConnState on the outlier_detection balancer returns the wrapped SwitchTo error.\nif err := od.UpdateClientConnState(state); err != nil {\n    if strings.Contains(err.Error(), \"error switching to child of type\") {\n        // the previous child remains active; investigate the inner %v\n    }\n}","preventionTips":["Ensure the child balancer's Build can succeed in your environment before configuring outlier_detection.","Test SwitchTo paths in isolation when introducing a new child policy type.","Read the inner error to find the Build failure cause."],"tags":["go","grpc","xds","balancer","gracefulswitch","outlier-detection"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}