grpc/grpc-go · error

least-request: lbConfig.choiceCount

Error message

least-request: lbConfig.choiceCount: %v, must be >= 2

What it means

Returned by least_request_experimental's ParseConfig() (balancer/leastrequest/leastrequest.go:73). Per gRFC A48, choiceCount must be >= 2. When unset, the code defaults it to 2 (line 66), so this error only fires when choiceCount is explicitly set to 0 or 1. Values > 10 are silently clamped to 10, but values < 2 are rejected outright.

Solutions

  1. Set choiceCount to a value between 2 and 10, or omit it entirely to use the default of 2.
  2. If you want the fewest samples, use exactly 2.
  3. Add a config validator that rejects choiceCount < 2 before deployment.

Example fix

// before: explicitly sets choiceCount to 1 — rejected
grpc.WithDefaultServiceConfig(`{"loadBalancingConfig":[{"least_request_experimental":{"choiceCount":1}}]}`)

// after: omit choiceCount (defaults to 2) or set it to >= 2
grpc.WithDefaultServiceConfig(`{"loadBalancingConfig":[{"least_request_experimental":{"choiceCount":2}}]}`)
Defensive patterns

Strategy: validation

Validate before calling

// Reject choiceCount < 2 before sending the config.
func validateChoiceCount(c uint32) error {
    if c < 2 {
        return fmt.Errorf("choiceCount %d must be >= 2", c)
    }
    return nil
}

Prevention

When it happens

Trigger: The least_request_experimental config JSON explicitly sets choiceCount to 0 or 1, hitting the check at line 72 (lbConfig.ChoiceCount < 2). ParseConfig returns this error, failing service config application.

Common situations: Operator misconfigures choiceCount thinking lower means less overhead; a config template uses 0 meaning 'default' (but 0 is rejected, not defaulted — only omitted is defaulted); xDS control plane pushes an out-of-range value.

Related errors


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

Appendix: source

Thrown at balancer/leastrequest/leastrequest.go:73

	// ChoiceCount is the number of random SubConns to sample to find the one
	// with the fewest outstanding requests. If unset, defaults to 2. If set to
	// < 2, the config will be rejected, and if set to > 10, will become 10.
	ChoiceCount uint32 `json:"choiceCount,omitempty"`
}

type bb struct{}

func (bb) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
	lbConfig := &LBConfig{
		ChoiceCount: 2,
	}
	if err := json.Unmarshal(s, lbConfig); err != nil {
		return nil, fmt.Errorf("least-request: unable to unmarshal LBConfig: %v", err)
	}
	// "If `choice_count < 2`, the config will be rejected." - A48
	if lbConfig.ChoiceCount < 2 { // sweet
		return nil, fmt.Errorf("least-request: lbConfig.choiceCount: %v, must be >= 2", lbConfig.ChoiceCount)
	}
	// "If a LeastRequestLoadBalancingConfig with a choice_count > 10 is
	// received, the least_request_experimental policy will set choice_count =
	// 10." - A48
	if lbConfig.ChoiceCount > 10 {
		lbConfig.ChoiceCount = 10
	}
	return lbConfig, nil
}

func (bb) Name() string {
	return Name
}

func (bb) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Balancer {
	b := &leastRequestBalancer{
		ClientConn:        cc,
		endpointRPCCounts: resolver.NewEndpointMap[*atomic.Int32](),

View on GitHub (pinned to 0c51461d27)