grpc/grpc-go · error
outlier detection: error switching to child of type
Error message
outlier detection: error switching to child of type %q: %v
What it means
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.
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.
Defensive patterns
Strategy: try-catch
Try / catch
// UpdateClientConnState on the outlier_detection balancer returns the wrapped SwitchTo error.
if err := od.UpdateClientConnState(state); err != nil {
if strings.Contains(err.Error(), "error switching to child of type") {
// the previous child remains active; investigate the inner %v
}
} Prevention
- 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.
When it happens
Trigger: b.child.switchTo(bb) returns a non-nil error from gracefulswitch.Balancer.SwitchTo, which propagates the underlying builder.Build error.
Common situations: Child balancer Build() fails (invalid build options, resource exhaustion, init errors); custom balancer whose Build returns an error; rare concurrent switch paths.
Related errors
- outlier detection: child balancer
- xds: unable to unmarshal LBconfig
- child policy not registered
- failed to JSON marshal load balancing policy for child
- failed to parse load balancing policy for child
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/131ce36ba8643844.
Report an issue: GitHub.
Appendix: source
Thrown at internal/xds/balancer/outlierdetection/balancer.go:307
if !ok {
b.logger.Errorf("received config with unexpected type %T: %v", s.BalancerConfig, s.BalancerConfig)
return balancer.ErrBadResolverState
}
// Reject whole config if child policy doesn't exist, don't persist it for
// later.
bb := balancer.Get(lbCfg.ChildPolicy.Name)
if bb == nil {
return fmt.Errorf("outlier detection: child balancer %q not registered", lbCfg.ChildPolicy.Name)
}
// It is safe to read b.cfg here without holding the mutex, as the only
// write to b.cfg happens later in this function. This function is part of
// the balancer.Balancer API, so it is guaranteed to be called in a
// synchronous manner, so it cannot race with this read.
if b.cfg == nil || b.cfg.ChildPolicy.Name != lbCfg.ChildPolicy.Name {
if err := b.child.switchTo(bb); err != nil {
return fmt.Errorf("outlier detection: error switching to child of type %q: %v", lbCfg.ChildPolicy.Name, err)
}
}
b.mu.Lock()
// Inhibit child picker updates until this UpdateClientConnState() call
// completes. If needed, a picker update containing the no-op config bit
// determined from this config and most recent state from the child will be
// sent synchronously upward at the end of this UpdateClientConnState()
// call.
b.inhibitPickerUpdates = true
b.updateUnconditionally = false
b.cfg = lbCfg
newEndpoints := resolver.NewEndpointMap[bool]()
for _, ep := range s.ResolverState.Endpoints {
newEndpoints.Set(ep, true)
if _, ok := b.endpoints.Get(ep); !ok {
b.endpoints.Set(ep, newEndpointInfo())View on GitHub (pinned to 0c51461d27)