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
- Wrap the rate-limited policy in a Retry policy whose backoff reads exception.RetryAfter.
- Pass a retryAfterFactory to RateLimit to return a fallback TResult instead of throwing.
- Right-size numberOfExecutions and maxBurst from measured throughput.
- 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
- Wrap RateLimit in a Retry policy that backs off using exception.RetryAfter.
- Consider a retryAfterFactory to convert rejection into a fallback TResult.
- Tune numberOfExecutions and maxBurst from measured load.
- Instrument RateLimitRejectedException rates to detect under-provisioned limits.
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
- The operation has been rate-limited and should be retried af
- Value cannot be null. (Parameter 'action')
- numberOfExecutions per timespan must be an integer greater t
- perTimeSpan must be a positive timespan.
- maxBurst must be an integer greater than or equal to 1.
AI-assisted analysis of App-vNext/Polly@d0e46bdb1e (2026-08-13).
Data as JSON: /api/errors/b061fd19a5335044.
Report an issue: GitHub.