grpc/grpc-go · error

failed to marshal child policy config %+v: %v

Error message

failed to marshal child policy config %+v: %v

What it means

Emitted from rlsBalancer.buildAndPushChildPolicyConfigs when json.Marshal(config) fails on the map[string]json.RawMessage child policy config (balancer.go:466). The config map was already validated at ParseConfig time, so this branch is largely defensive; it would fire only if a json.RawMessage value in the stored map is not valid JSON, which the parse-time re-marshal normally catches. On failure the affected childPolicyWrapper goes lame-duck (lamify).

Source

Thrown at balancer/rls/balancer.go:467

// buildAndPushChildPolicyConfigs builds the final child policy configuration by
// adding the `targetField` to the base child policy configuration received in
// RLS LB policy configuration. The `targetField` is set to target and
// configuration is pushed to the child policy through the BalancerGroup.
//
// Caller must hold lb.stateMu.
func (b *rlsBalancer) buildAndPushChildPolicyConfigs(target string, newCfg *lbConfig, ccs *balancer.ClientConnState) error {
	jsonTarget, err := json.Marshal(target)
	if err != nil {
		return fmt.Errorf("failed to marshal child policy target %q: %v", target, err)
	}

	config := newCfg.childPolicyConfig
	targetField := newCfg.childPolicyTargetField
	config[targetField] = jsonTarget
	jsonCfg, err := json.Marshal(config)
	if err != nil {
		return fmt.Errorf("failed to marshal child policy config %+v: %v", config, err)
	}

	parser, _ := b.childPolicyBuilder.(balancer.ConfigParser)
	parsedCfg, err := parser.ParseConfig(jsonCfg)
	if err != nil {
		return fmt.Errorf("childPolicy config parsing failed: %v", err)
	}

	state := balancer.ClientConnState{ResolverState: ccs.ResolverState, BalancerConfig: parsedCfg}
	b.logger.Infof("Pushing new state to child policy %q: %+v", target, state)
	if err := b.bg.UpdateClientConnState(target, state); err != nil {
		b.logger.Warningf("UpdateClientConnState(%q, %+v) failed : %v", target, ccs, err)
	}
	return nil
}

func (b *rlsBalancer) ResolverError(err error) {
	b.bg.ResolverError(err)

View on GitHub (pinned to 0c51461d27)

Solutions

  1. Ensure the lbConfig returned from ParseConfig is treated as immutable; do not mutate childPolicyConfig outside the balancer's own UpdateClientConnState path.
  2. Run against upstream google.golang.org/grpc rather than a fork that may have removed the parse-time validation step.
  3. If authoring the child policy config by hand, validate it with a JSON linter and through the child policy's own ParseConfig before shipping.

Example fix

// before: mutating the parsed config externally
rlsCfg.childPolicyConfig["custom"] = json.RawMessage("not-json")

// after: treat parsed config as immutable; only the balancer writes the target field
// (no external mutation)
Defensive patterns

Strategy: validation

Validate before calling

// Confirm every RawMessage in the stored child-policy config is valid JSON
// before the runtime path re-marshals it.
func childConfigIsMarshallable(m map[string]json.RawMessage) bool {
    for k, v := range m {
        if !json.Valid(v) {
            return false
        }
        _ = k
    }
    _, err := json.Marshal(m)
    return err == nil
}

Type guard

func isImmutableChildConfig(m map[string]json.RawMessage) bool {
    _, err := json.Marshal(m)
    return err == nil
}

Prevention

When it happens

Trigger: The stored childPolicyConfig map contains a json.RawMessage entry that is not valid JSON when re-marshaled at runtime after the target field is overwritten. Reachable in practice only via a race that mutates the stored map between ParseConfig and UpdateClientConnState, or a build/version skew where the stored RawMessage was never parse-validated.

Common situations: Concurrent unsynchronized mutation of the lbConfig.childPolicyConfig map from another goroutine; use of reflect/unsafe to inject raw bytes; a fork of the RLS balancer that skips parse-time validation.

Related errors


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