grpc/grpc-go · error

RLS response's target list does not contain any entries for

Error message

RLS response's target list does not contain any entries for key %+v

What it means

When the RLS server returns an OK status but an empty targets list, the RLS protocol considers the response a failure (a successful response must name at least one backend target). handleRouteLookupResponse synthesizes this error and, because err != nil, puts the cache entry into backoff instead of installing targets. It is an internal/control-plane condition surfaced through backoff and metrics, not normally thrown directly at the caller.

Source

Thrown at balancer/rls/picker.go:320

	// - status is set to error returned from the control channel
	// - current backoff state is available in the pending entry
	//   - `retries` field is incremented and
	//   - backoff state is moved to the data cache
	// - backoffTime is set to the time indicated by the backoff state
	// - backoffExpirationTime is set to twice the backoff time
	// - backoffTimer is set to fire after backoffTime
	//
	// When a proactive cache refresh fails, this would leave the targets and the
	// expiry time from the old entry unchanged. And this mean that the old valid
	// entry would be used until expiration, and a new picker would be sent upon
	// backoff expiry.
	now := time.Now()

	// "An RLS request is considered to have failed if it returns a non-OK
	// status or the RLS response's targets list is non-empty." - RLS LB Policy
	// design.
	if len(targets) == 0 && err == nil {
		err = fmt.Errorf("RLS response's target list does not contain any entries for key %+v", cacheKey)
		// If err is set, rpc error from the control plane and no control plane
		// configuration is why no targets were passed into this helper, no need
		// to specify and tell the user this information.
	}
	if err != nil {
		dcEntry.status = err
		pendingEntry := p.lb.pendingMap[cacheKey]
		pendingEntry.retries++
		backoffTime := pendingEntry.bs.Backoff(pendingEntry.retries)
		dcEntry.backoffState = pendingEntry
		dcEntry.backoffTime = now.Add(backoffTime)
		dcEntry.backoffExpiryTime = now.Add(2 * backoffTime)
		if dcEntry.backoffState.timer != nil {
			dcEntry.backoffState.timer.Stop()
		}
		dcEntry.backoffState.timer = time.AfterFunc(backoffTime, p.lb.sendNewPicker)
		return
	}

View on GitHub (pinned to 03255a9237)

Solutions

  1. Configure a defaultTarget in RouteLookupConfig so the RLS balancer falls back gracefully when this happens (useDefaultPickIfPossible).
  2. On the server side, verify the RLS key -> target mapping covers the keys your key builders emit.
  3. Inspect RLS server logs/metrics to confirm which key is returning no targets, then add the mapping.

Example fix

// before
"routeLookupConfig": { "lookupService": "rls:443", /* no defaultTarget */ }
// after
"routeLookupConfig": { "lookupService": "rls:443", "defaultTarget": "fallback-cluster" }
Defensive patterns

Strategy: fallback

Validate before calling

func hasDefaultTarget(cfg *rlspb.RouteLookupConfig) bool {
    return cfg.GetDefaultTarget() != ""
}

Prevention

When it happens

Trigger: The RLS control plane accepts the RouteLookup RPC, returns OK with status nil, but the targets array in the response is empty (or the control channel dropped targets on the floor). Repeats trigger backoff and default-target fallback.

Common situations: RLS server misconfiguration (no mapping for the key); key builder producing keys the server does not recognize; partial rollout where the RLS data plane is up but its config is empty; the server returning headerData but no targets.

Related errors


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