grpc/grpc-go · error

ringhash: expected xDS config selector to set the request ha

Error message

ringhash: expected xDS config selector to set the request hash

What it means

Returned at RPC time by ringhash picker.Pick (picker.go:55-62) when requestHashHeader is empty AND no xDS-provided request hash is present in the RPC context. When requestHashHeader is empty, ringhash expects xDS (via a config selector on the channel) to inject the hash through iringhash.XDSRequestHash; if that returns ok==false the pick cannot place the RPC deterministically on the ring and fails.

Source

Thrown at balancer/ringhash/picker.go:61

	// requestHashHeader is the header key to look for the request hash. If it's
	// empty, the request hash is expected to be set in the context via xDS.
	// See gRFC A76.
	requestHashHeader string

	// hasEndpointInConnectingState is true if any of the endpoints is in
	// CONNECTING.
	hasEndpointInConnectingState bool

	randUint64 func() uint64
}

func (p *picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
	usingRandomHash := false
	var requestHash uint64
	if p.requestHashHeader == "" {
		var ok bool
		if requestHash, ok = iringhash.XDSRequestHash(info.Ctx); !ok {
			return balancer.PickResult{}, fmt.Errorf("ringhash: expected xDS config selector to set the request hash")
		}
	} else {
		md, ok := metadata.FromOutgoingContext(info.Ctx)
		if !ok || len(md.Get(p.requestHashHeader)) == 0 {
			requestHash = p.randUint64()
			usingRandomHash = true
		} else {
			values := strings.Join(md.Get(p.requestHashHeader), ",")
			requestHash = xxhash.Sum64String(values)
		}
	}

	e := p.ring.pick(requestHash)
	ringSize := len(p.ring.items)
	if !usingRandomHash {
		// Per gRFC A61, because of sticky-TF with PickFirst's auto reconnect on TF,
		// we ignore all TF subchannels and find the first ring entry in READY,
		// CONNECTING or IDLE.  If that entry is in IDLE, we need to initiate a

View on GitHub (pinned to 03255a9237)

Solutions

  1. If you are not using xDS, set requestHashHeader in the ring_hash config to a header you attach to each RPC, so the picker hashes that header instead of requiring xDS.
  2. If using xDS, ensure the route has a hash_policy (e.g. header/hash-all/request_path) so the config selector sets the request hash.
  3. If you intentionally want random placement, note ringhash is the wrong policy; use round_robin instead.

Example fix

// before: non-xDS channel, ring_hash with empty requestHashHeader
raw := `{"minRingSize":1024,"maxRingSize":4096}` // every Pick() fails with this error

// after: provide an explicit hash header (A76)
raw := `{"minRingSize":1024,"maxRingSize":4096,"requestHashHeader":"user-id"}`
// and attach the header on each RPC:
ctx := metadata.AppendToOutgoingContext(ctx, "user-id", strconv.Itoa(uid))
Defensive patterns

Strategy: fallback

Validate before calling

// Ensure ring_hash is usable without xDS by always setting a hash header.
func ensureRingHashHeader(cfg map[string]any) {
    if _, ok := cfg["requestHashHeader"]; !ok || cfg["requestHashHeader"] == "" {
        cfg["requestHashHeader"] = "x-request-hash" // your app must set this header
    }
}

Type guard

func ringHashHasHashSource(cfg map[string]any, xdsProvidesHash bool) bool {
    h, _ := cfg["requestHashHeader"].(string)
    return h != "" || xdsProvidesHash
}

Try / catch

// This fails at Pick() time, not as a thrown exception. Prevent by config;
// at call time you can still set the metadata header:
ctx := metadata.AppendToOutgoingContext(ctx, "user-id", uid)

Prevention

When it happens

Trigger: Using the ring_hash policy directly (not through xDS) without setting requestHashHeader. The RPC was made on a channel whose xDS config selector did not set the request hash (e.g. a direct dial that bypasses xDS, or an xDS update missing the hash-on field).

Common situations: Manually configuring ring_hash via service config on a non-xDS channel and forgetting requestHashHeader. xDS route configuration lacking a hash_policy so the selector never injects a hash.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/78c3f3d29bb836c9. Report an issue: GitHub.