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

  1. Check the underlying error (%v) — it usually names the missing dependency or builder
  2. Ensure the child balancer and its dependencies are imported/registered
  3. 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

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


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/131ce36ba8643844. Report an issue: GitHub.