grpc/grpc-go · error
outlier detection: error switching to child of type %q: %v
Error message
outlier detection: error switching to child of type %q: %v
What it means
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.
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 03255a9237)
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
Defensive patterns
Strategy: try-catch
Try / catch
if err := bal.UpdateClientConnState(state); err != nil {
var se interface{ Unwrap() error }
if errors.As(err, &se) {
logger.Errorf("child switch failed; retaining prior child: %v", se.Unwrap())
}
return err // gRPC core will retry with the next resolver update
} Prevention
- Import child balancer packages and their dependencies
- Avoid reconfiguring a balancer that has been Closed
- Inspect the wrapped error for the missing builder/dependency
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- OutlierDetectionLoadBalancingConfig.interval = %s; must be >
- OutlierDetectionLoadBalancingConfig.base_ejection_time = %s;
- OutlierDetectionLoadBalancingConfig.max_ejection_time = %s;
- OutlierDetectionLoadBalancingConfig.max_ejection_percent = %
- OutlierDetectionLoadBalancingConfig.SuccessRateEjection.enfo
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/131ce36ba8643844.
Report an issue: GitHub.