App-vNext/Polly · warning · RateLimitRejectedException

The operation has been rate-limited and should be retried af

Error message

The operation has been rate-limited and should be retried after {retryAfter}

What it means

Thrown by RateLimitEngine.Implementation (synchronous) when the rate limiter denies execution and no retryAfterFactory was supplied. The RateLimitRejectedException carries RetryAfter computed by the token-bucket limiter. This is the sync counterpart of error 147 and represents an intentional throttle signal, not a fault.

Source

Thrown at src/Polly/RateLimit/RateLimitEngine.cs:25

        IRateLimiter rateLimiter,
        Func<TimeSpan, Context, TResult>? retryAfterFactory,
        Func<Context, CancellationToken, TResult> action,
        Context context,
        CancellationToken cancellationToken)
    {
        (bool permit, TimeSpan retryAfter) = rateLimiter.PermitExecution();

        if (permit)
        {
            return action(context, cancellationToken);
        }

        if (retryAfterFactory != null)
        {
            return retryAfterFactory(retryAfter, context);
        }

        throw new RateLimitRejectedException(retryAfter);
    }
}

View on GitHub (pinned to d0e46bdb1e)

Solutions

  1. Wrap the rate-limited policy in a Retry policy whose backoff reads exception.RetryAfter.
  2. Pass a retryAfterFactory to RateLimit to return a fallback TResult instead of throwing.
  3. Right-size numberOfExecutions and maxBurst from measured throughput.
  4. Catch RateLimitRejectedException at the call site and back off using RetryAfter.

Example fix

// before
var rl = Policy.RateLimit(10, TimeSpan.FromSeconds(1), 10);
rl.Execute(ctx => doWork(ctx), new Context(), CancellationToken.None);

// after
var retry = Policy.Handle<RateLimitRejectedException>()
    .WaitAndRetry((_, ex) => ((RateLimitRejectedException)ex).RetryAfter.Add(TimeSpan.FromMilliseconds(50)));
var wrap = Policy.Wrap(retry, rl);
wrap.Execute(ctx => doWork(ctx), new Context(), CancellationToken.None);
Defensive patterns

Strategy: fallback

Validate before calling

// Either supply a retryAfterFactory or wrap in retry
var rl = Policy.RateLimit<TResult>(10, TimeSpan.FromSeconds(1), 10,
    (retryAfter, ctx) => /* fallback value */ default);
rl.Execute(func, context, ct);

Try / catch

try { rl.Execute(func, context, ct); }
catch (RateLimitRejectedException ex) {
    Thread.Sleep(ex.RetryAfter.Add(TimeSpan.FromMilliseconds(50)));
    // retry or fall back
}

Prevention

When it happens

Trigger: A synchronous RateLimitPolicy (no retryAfterFactory) executes a delegate while the bucket is empty; PermitExecution returns (false, retryAfter), retryAfterFactory is null, so the engine throws RateLimitRejectedException(retryAfter). Sustained load exceeding the configured limit triggers it.

Common situations: Load above the configured numberOfExecutions/perTimeSpan; maxBurst too low for traffic spikes; no retry policy wrapped around the rate-limited call; a retryAfterFactory intended to absorb rejection was omitted.

Related errors


AI-assisted analysis of App-vNext/Polly@d0e46bdb1e (2026-08-13). Data as JSON: /api/errors/b061fd19a5335044. Report an issue: GitHub.