grpc/grpc-go · error

xds_wrr_locality: invalid LBConfig

Error message

xds_wrr_locality: invalid LBConfig: %s, error: %v

What it means

Thrown by the xDS wrr_locality (weighted round-robin locality) custom LB balancer when ParseConfig cannot json.Unmarshal its service config. The config is expected to be a JSON object containing a 'childPolicy' field (per gRFC A52). If the raw JSON handed to the balancer is malformed or the wrong shape, unmarshalling fails here.

Solutions

  1. Inspect the full error string: it embeds the offending raw JSON (the %s). Compare it against the LBConfig struct {childPolicy} shape.
  2. Validate the wrr_locality config block in your LDS/CDS response or service config is a JSON object with a childPolicy field before publishing.
  3. Confirm gRPC client and xDS server support the same xDS custom LB policy version (A52).
  4. If you are unit-testing this balancer directly, ensure you pass json.RawMessage of a valid JSON object, e.g. {"childPolicy":[{"round_robin":{}}]}.

Example fix

// before (bad raw config passed to balancer)
cfg := json.RawMessage(`not-json`)
parsed, err := bb.ParseConfig(cfg) // returns error 380

// after
cfg := json.RawMessage(`{"childPolicy":[{"round_robin":{}}]}`)
parsed, err := bb.ParseConfig(cfg)
Defensive patterns

Strategy: validation

Validate before calling

// Validate an xds_wrr_locality LB config block before handing it to the balancer.
func validateWRRLocalityConfig(raw json.RawMessage) error {
    var cfg struct {
        ChildPolicy json.RawMessage `json:"childPolicy"`
    }
    if err := json.Unmarshal(raw, &cfg); err != nil {
        return fmt.Errorf("wrr_locality config is not valid JSON: %w", err)
    }
    if len(cfg.ChildPolicy) == 0 {
        return fmt.Errorf("wrr_locality config missing childPolicy")
    }
    return nil
}

Type guard

// Narrow a parsed service config to the wrr_locality LBConfig type.
func isWRRLocalityLBConfig(cfg serviceconfig.LoadBalancingConfig) (*wrrlocality.LBConfig, bool) {
    lb, ok := cfg.(*wrrlocality.LBConfig)
    return lb, ok && lb != nil && lb.ChildPolicy != nil
}

Try / catch

// ParseConfig returns an error, not a panic. Handle it at the resolver/config layer.
parsed, err := bb.ParseConfig(raw)
if err != nil {
    return fmt.Errorf("reject service config, bad wrr_locality block: %w", err)
}

Prevention

When it happens

Trigger: Called when the gRPC core invokes bb.ParseConfig(s json.RawMessage) at balancer.go:92 with a value that fails json.Unmarshal into *LBConfig. This is driven by the xDS resolver translating a service config whose xds_wrr_locality_experimental LB config block is not valid JSON or lacks the expected struct shape.

Common situations: xDS control plane pushes a malformed wrr_locality config block; service config version skew between server and client; manually constructing a service config JSON that omits the object wrapper or passes a non-object value for childPolicy.

Related errors


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

Appendix: source

Thrown at internal/xds/balancer/wrrlocality/balancer.go:95

	wtbCfgParser, ok := builder.(balancer.ConfigParser)
	if !ok {
		// Shouldn't happen, imported weighted target builder has this method.
		return nil
	}
	wrrL := &wrrLocalityBalancer{
		child:       wtb,
		childParser: wtbCfgParser,
	}

	wrrL.logger = prefixLogger(wrrL)
	wrrL.logger.Infof("Created")
	return wrrL
}

func (bb) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
	var lbCfg *LBConfig
	if err := json.Unmarshal(s, &lbCfg); err != nil {
		return nil, fmt.Errorf("xds_wrr_locality: invalid LBConfig: %s, error: %v", string(s), err)
	}
	if lbCfg == nil || lbCfg.ChildPolicy == nil {
		return nil, errors.New("xds_wrr_locality: invalid LBConfig: child policy field must be set")
	}
	return lbCfg, nil
}

type attributeKey struct{}

// Equal allows the values to be compared by Attributes.Equal.
func (a AddrInfo) Equal(o any) bool {
	oa, ok := o.(AddrInfo)
	return ok && oa.LocalityWeight == a.LocalityWeight
}

// AddrInfo is the locality weight of the locality an address is a part of.
type AddrInfo struct {
	LocalityWeight uint32

View on GitHub (pinned to 0c51461d27)