grpc/grpc-go · error
could not switch to new child balancer: %w
Error message
could not switch to new child balancer: %w
What it means
Returned by gracefulswitch.Balancer.UpdateClientConnState when the internal switchTo() call fails. The wrapped error (%w) is either errBalancerClosed ('gracefulSwitchBalancer is closed', gracefulswitch.go:115) when the balancer has already been Closed, or balancer.ErrBadResolverState when the child builder's Build() returns nil (gracefulswitch.go:138-148). This occurs when the config specifies a child that differs from the current one.
Source
Thrown at internal/balancer/gracefulswitch/gracefulswitch.go:186
}
// UpdateClientConnState forwards the update to the latest balancer created.
//
// If the state's BalancerConfig is the config returned by a call to
// gracefulswitch.ParseConfig, then this function will automatically SwitchTo
// the balancer indicated by the config before forwarding its config to it, if
// necessary.
func (gsb *Balancer) UpdateClientConnState(state balancer.ClientConnState) error {
// The resolver data is only relevant to the most recent LB Policy.
balToUpdate := gsb.latestBalancer()
gsbCfg, ok := state.BalancerConfig.(*lbConfig)
if ok {
// Switch to the child in the config unless it is already active.
if balToUpdate == nil || gsbCfg.childBuilder.Name() != balToUpdate.builder.Name() {
var err error
balToUpdate, err = gsb.switchTo(gsbCfg.childBuilder)
if err != nil {
return fmt.Errorf("could not switch to new child balancer: %w", err)
}
}
// Unwrap the child balancer's config.
state.BalancerConfig = gsbCfg.childConfig
}
if balToUpdate == nil {
return errBalancerClosed
}
// Perform this call without gsb.mu to prevent deadlocks if the child calls
// back into the channel. The latest balancer can never be closed during a
// call from the channel, even without gsb.mu held.
return balToUpdate.UpdateClientConnState(state)
}
// ResolverError forwards the error to the latest balancer created.
func (gsb *Balancer) ResolverError(err error) {View on GitHub (pinned to 0c51461d27)
Solutions
- Check the wrapped error with errors.Is(err, errBalancerClosed) to distinguish closed vs bad builder
- Ensure UpdateClientConnState is not called after Close() on the gracefulswitch Balancer
- If using a custom balancer.Builder, ensure Build() never returns nil
- Synchronize Close() and UpdateClientConnState calls if they can race
Example fix
// before: no error check on UpdateClientConnState after potential close
err = gsb.UpdateClientConnState(state)
if err != nil {
log.Printf("error: %v", err)
}
// after: distinguish close vs real error
err = gsb.UpdateClientConnState(state)
if err != nil {
if errors.Is(err, gracefulswitch.ErrBalancerClosed) {
return // expected during shutdown
}
log.Printf("switch failed: %v", err)
} Defensive patterns
Strategy: try-catch
Try / catch
// Handle UpdateClientConnState errors from gracefulswitch, distinguishing close vs real errors
err := gsb.UpdateClientConnState(state)
if err != nil {
if errors.Is(err, errBalancerClosed) {
// Expected during shutdown; safe to ignore
return nil
}
// Real error: bad builder or other failure
return fmt.Errorf("balancer update failed: %w", err)
} Prevention
- Never call UpdateClientConnState after Close(); synchronize these operations
- If using a custom balancer.Builder, ensure Build() never returns nil
- Use errors.Is to distinguish errBalancerClosed from balancer.ErrBadResolverState in the wrapped error
When it happens
Trigger: Calling UpdateClientConnState with a BalancerConfig (of type *lbConfig from ParseConfig) whose childBuilder.Name() differs from the current balancer, and switchTo() fails. This happens when Close() was called concurrently or beforehand (gracefulswitch.go:113-116), or when builder.Build() returns nil (gracefulswitch.go:137-148).
Common situations: A lifecycle race where the parent gRPC channel or balancer is shut down while a resolver update is in flight, triggering UpdateClientConnState after Close(). A custom balancer builder that incorrectly returns nil from Build().
Related errors
- balancergroup: already closed
- xds: the xDS client is closed
- expected a JSON struct with one entry; received entry %v at
- error parsing config for policy %q: %v
- no supported policies found in config: %v
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/ee52e675fe4c362a.
Report an issue: GitHub.