Repository: grpc/grpc-go
File: xds/internal/balancer/clusterimpl/clusterimpl.go (Go)

Code before:
		c := xdsclient.FromResolverState(s.ResolverState)
		if c == nil {
			return balancer.ErrBadResolverState
		}
		b.xdsClient = c
	}

	// Update load reporting config. This needs to be done before updating the
	// child policy because we need the loadStore from the updated client to be
	// passed to the ccWrapper, so that the next picker from the child policy
	// will pick up the new loadStore.
	if err := b.updateLoadStore(newConfig); err != nil {
		return err
	}

	if b.config == nil || b.config.ChildPolicy.Name != newConfig.ChildPolicy.Name {
		if b.config == nil || b.config.ChildPolicy.Name != newConfig.ChildPolicy.Name {
			strCfg, err := newConfig.ChildPolicy.MarshalJSON()
			if err != nil {
				return fmt.Errorf("error marshaling config: %v", err)
			}
			r := json.RawMessage(strCfg)
			sc, err := gracefulswitch.ParseConfig(r)
			if err != nil {
				return fmt.Errorf("error parsing child config: %v", err)
			}
			if err := b.child.UpdateClientConnState(balancer.ClientConnState{
				ResolverState:  s.ResolverState,
				BalancerConfig: sc,
			}); err != nil {
				return fmt.Errorf("error switching to child of type %q: %v", newConfig.ChildPolicy.Name, err)
			}
		}
	}
	b.config = newConfig

	// Notify run() of this new config, in case drop and request counter need
	// update (which means a new picker needs to be generated).
	b.pickerUpdateCh.Put(newConfig)

	// Addresses and sub-balancer config are sent to sub-balancer.
	return b.child.UpdateClientConnState(balancer.ClientConnState{
		ResolverState:  s.ResolverState,
		BalancerConfig: b.config.ChildPolicy.Config,
	})
}

func (b *clusterImplBalancer) ResolverError(err error) {
	if b.closed.HasFired() {
		b.logger.Warningf("xds: received resolver error {%+v} after clusterImplBalancer was closed", err)
		return

Review comment (suggestion):
What we want is something like this where we the JSON configuration passed to the gracefulswitch balancer is an array of LB policy configurations (but with a single entry, one corresponding to the child policy that we have).

```golang
       // Build config for the gracefulswitch balancer. It is safe to ignore JSON
       // marshaling errors here, since the config was already validated as part of
       // ParseConfig().
       cfg := []map[string]any{{newConfig.ChildPolicy.Name: newConfig.ChildPolicy.Config}}
       cfgJSON, _ := json.Marshal(cfg)
       parsedCfg, err := gracefulswitch.ParseConfig(cfgJSON)
       if err != nil {
               return err
        }
```

Code after:
	// Build config for the gracefulswitch balancer. It is safe to ignore JSON
	// marshaling errors here, since the config was already validated as part of
	// ParseConfig().
	cfg := []map[string]any{{newConfig.ChildPolicy.Name: newConfig.ChildPolicy.Config}}
	cfgJSON, _ := json.Marshal(cfg)
	parsedCfg, err := gracefulswitch.ParseConfig(cfgJSON)
	if err != nil {
		return err
	}

	b.config = newConfig

	b.telemetryLabels = newConfig.TelemetryLabels
	dc := b.handleDropAndRequestCount(newConfig)
	if dc != nil && b.childState.Picker != nil {
		b.ClientConn.UpdateState(balancer.State{
			ConnectivityState: b.childState.ConnectivityState,
			Picker:            b.newPicker(dc),
		})
	}

	// Addresses and sub-balancer config are sent to sub-balancer.
	return b.child.UpdateClientConnState(balancer.ClientConnState{
		ResolverState:  s.ResolverState,
		BalancerConfig: parsedCfg,
	})
}

func (b *clusterImplBalancer) UpdateClientConnState(s balancer.ClientConnState) error {
	// Handle the update in a blocking fashion.
	errCh := make(chan error, 1)
	callback := func(context.Context) {
		errCh <- b.updateClientConnState(s)
	}
	onFailure := func() {
		// An attempt to schedule callback fails only when an update is received
		// after Close().
		errCh <- errBalancerClosed
	}
	b.serializer.ScheduleOr(callback, onFailure)
	return <-errCh
}

func (b *clusterImplBalancer) ResolverError(err error) {
	b.serializer.TrySchedule(func(context.Context) {
		b.child.ResolverError(err)
	})
}

func (b *clusterImplBalancer) updateSubConnState(_ balancer.SubConn, s balancer.SubConnState, cb func(balancer.SubConnState)) {
	// Trigger re-resolution when a SubConn turns transient failure. This is