grpc/grpc-go · error

failed to JSON marshal load balancing policy for child

Error message

failed to JSON marshal load balancing policy for child %q: %v

What it means

When a child policy's type changes in cluster_manager (clustermanager.go:113-122), the new ChildPolicy must be re-serialized to JSON so balancergroup.ParseConfig can drive a graceful switch. If childCfg.ChildPolicy.MarshalJSON fails, cluster_manager records the error, installs an error picker for that one child via setErrorPickerForChild, and continues processing the rest. Only RPCs routed to the affected child fail.

Solutions

  1. Inspect the raw bytes of ChildPolicy.Config for malformed JSON before deployment.
  2. If using a custom balancer, ensure MarshalJSON never errors on valid in-memory configs.
  3. Treat this child as broken: cluster_manager isolates the failure, so other children keep serving while you fix the source.
Defensive patterns

Strategy: try-catch

Try / catch

// UpdateClientConnState on cluster_manager returns the wrapped error; isolate per-child failures.
if err := cm.UpdateClientConnState(state); err != nil {
    if strings.Contains(err.Error(), "failed to JSON marshal load balancing policy for child") {
        // log the child name from the error, continue serving other children
    }
}

Prevention

When it happens

Trigger: childCfg.ChildPolicy.Config holds a json.RawMessage with corrupt/invalid bytes, or a custom ChildPolicy type whose MarshalJSON returns an error during the policy-type-change path (newPolicyName != oldPolicyName).

Common situations: Hand-crafted ChildPolicy.Config with invalid raw JSON bytes; bug in a custom child balancer's MarshalJSON; rare race in xDS resource translation producing malformed raw messages.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/0348f842c22b4cd6. Report an issue: GitHub.

Appendix: source

Thrown at internal/xds/balancer/clustermanager/clustermanager.go:119

			// Add new sub-balancers to the aggregator and balancergroup.
			b.stateAggregator.add(childName)
			b.bg.Add(childName, balancer.Get(childCfg.ChildPolicy.Name))
		} else {
			// If the child policy type has changed for existing sub-balancers,
			// parse the new config and send down the config update to the
			// balancergroup, which will take care of gracefully switching the
			// child over to the new policy.
			//
			// If we run into errors here, we need to ensure that RPCs to this
			// child fail, while RPCs to other children with good configs
			// continue to succeed.
			newPolicyName, oldPolicyName := childCfg.ChildPolicy.Name, b.children[childName].ChildPolicy.Name
			if newPolicyName != oldPolicyName {
				var err error
				var cfgJSON []byte
				cfgJSON, err = childCfg.ChildPolicy.MarshalJSON()
				if err != nil {
					retErr = fmt.Errorf("failed to JSON marshal load balancing policy for child %q: %v", childName, err)
					b.setErrorPickerForChild(childName, retErr)
					continue
				}
				// This overwrites lbCfg to be in the format expected by the
				// gracefulswitch balancer. So, when this config is pushed to
				// the child (below), it will result in a graceful switch to the
				// new child policy.
				lbCfg, err = balancergroup.ParseConfig(cfgJSON)
				if err != nil {
					retErr = fmt.Errorf("failed to parse load balancing policy for child %q: %v", childName, err)
					b.setErrorPickerForChild(childName, retErr)
					continue
				}
			}
		}

		if err := b.bg.UpdateClientConnState(childName, balancer.ClientConnState{
			ResolverState: resolver.State{

View on GitHub (pinned to 0c51461d27)