grpc/grpc-go · error

wrr: unable to unmarshal LB policy config

Error message

wrr: unable to unmarshal LB policy config: %s, error: %v

What it means

The weighted_round_robin LB policy parses its JSON config via ParseConfig (balancer.go:120-131). If json.Unmarshal fails on the raw config bytes (malformed JSON or wrong field types), it returns this wrapped error. The config struct is lbConfig with fields enableOobLoadReport, oobReportingPeriod, blackoutPeriod, weightExpirationPeriod, weightUpdatePeriod, errorUtilizationPenalty.

Solutions

  1. Validate the JSON snippet shown in the error against the lbConfig schema (see config.go field names and JSON tags).
  2. Use the documented field names exactly: enableOobLoadReport, oobReportingPeriod, blackoutPeriod, weightExpirationPeriod, weightUpdatePeriod, errorUtilizationPenalty.
  3. Ensure durations are quoted Go time.Duration strings (e.g., "10s", "3m") and numbers are unquoted.

Example fix

// before
sc := `{"loadBalancingConfig":[{"weighted_round_robin":{"errorUtilizationPenalty":"1"}}]}` // value should be number
// after
sc := `{"loadBalancingConfig":[{"weighted_round_robin":{"errorUtilizationPenalty":1}}]}`
Defensive patterns

Strategy: validation

Validate before calling

func validateWRRConfigJSON(js string) error {
	cfg := &lbConfig{ErrorUtilizationPenalty: 1}
	dec := json.NewDecoder(strings.NewReader(js))
	dec.DisallowUnknownFields()
	return dec.Decode(cfg)
}

Prevention

When it happens

Trigger: A service config with a loadBalancingConfig entry for "weighted_round_robin" whose value is not valid JSON or whose fields have the wrong type (e.g., a string where a number/duration is expected). json.Unmarshal at balancer.go:129 returns a non-nil error.

Common situations: Hand-writing the WRR service config JSON; a typo such as a trailing comma or unquoted duration; passing a config intended for a different LB policy under the wrr name; version skew where a field was renamed.

Related errors


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

Appendix: source

Thrown at balancer/weightedroundrobin/balancer.go:130

	b.child = endpointsharding.NewBalancer(b, bOpts, balancer.Get(pickfirst.Name).Build, endpointsharding.Options{})
	b.logger = prefixLogger(b)
	if b.logger.V(2) {
		b.logger.Infof("Created")
	}
	return b
}

func (bb) ParseConfig(js json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
	lbCfg := &lbConfig{
		// Default values as documented in A58.
		OOBReportingPeriod:      iserviceconfig.Duration(10 * time.Second),
		BlackoutPeriod:          iserviceconfig.Duration(10 * time.Second),
		WeightExpirationPeriod:  iserviceconfig.Duration(3 * time.Minute),
		WeightUpdatePeriod:      iserviceconfig.Duration(time.Second),
		ErrorUtilizationPenalty: 1,
	}
	if err := json.Unmarshal(js, lbCfg); err != nil {
		return nil, fmt.Errorf("wrr: unable to unmarshal LB policy config: %s, error: %v", string(js), err)
	}

	if lbCfg.ErrorUtilizationPenalty < 0 {
		return nil, fmt.Errorf("wrr: errorUtilizationPenalty must be non-negative")
	}

	// For easier comparisons later, ensure the OOB reporting period is unset
	// (0s) when OOB reports are disabled.
	if !lbCfg.EnableOOBLoadReport {
		lbCfg.OOBReportingPeriod = 0
	}

	// Impose lower bound of 100ms on weightUpdatePeriod.
	if !internal.AllowAnyWeightUpdatePeriod && lbCfg.WeightUpdatePeriod < iserviceconfig.Duration(100*time.Millisecond) {
		lbCfg.WeightUpdatePeriod = iserviceconfig.Duration(100 * time.Millisecond)
	}

	return lbCfg, nil

View on GitHub (pinned to 0c51461d27)