Documentation Index
Fetch the complete documentation index at: https://mintlify.com/nimanikoo/Dotnet-RateLimiter/llms.txt
Use this file to discover all available pages before exploring further.
The ConcurrencyPolicy enforces a limit on the number of requests that are actively being processed at the same time, rather than on the number of requests arriving over a time period. A permit is acquired when a request enters the handler and released when the handler returns — regardless of how long that takes. This makes it the right tool for protecting resources that are sensitive to parallel load: databases, external HTTP services, expensive compute operations, and any endpoint that holds a shared resource lock.
How It Differs from Window-Based Limiting
Window-based policies (Fixed Window, Sliding Window, Token Bucket) answer the question: “How many requests has this caller made in the last N seconds?”
The Concurrency limiter answers a different question: “How many requests are running right now?”
| Dimension | Window-Based Policies | Concurrency Policy |
|---|
| Measures | Requests over a time period | Simultaneous in-flight requests |
| Permit lifecycle | Consumed per request | Held for the duration of the handler |
| Protects against | Request-rate spikes | Parallel resource exhaustion |
| Appropriate for | Public APIs, quota enforcement | DB-heavy endpoints, external calls |
Registration
The policy is registered inside AddCustomRateLimiter in RateLimiterExtensions.cs:
options.AddConcurrencyLimiter("ConcurrencyPolicy", opt =>
{
opt.PermitLimit = 7;
opt.QueueLimit = 3;
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
}).RejectionStatusCode = 429;
| Option | Value | Meaning |
|---|
PermitLimit | 7 | Maximum number of requests that can execute concurrently |
QueueLimit | 3 | Maximum number of requests that can wait while all permits are held |
QueueProcessingOrder | OldestFirst | The request that has been waiting the longest is admitted first when a permit frees up |
RejectionStatusCode | 429 | HTTP status returned when both the permit pool and queue are exhausted |
Applying the Policy
Decorate any controller action with [EnableRateLimiting("ConcurrencyPolicy")]. From TestController.cs:
[HttpGet("concurrency"), EnableRateLimiting("ConcurrencyPolicy")]
public IEnumerable<WeatherForecast> TestConcurrency() =>
Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
Queue Behavior
When all 7 permits are in use, incoming requests are not immediately rejected — they wait:
Requests 1–7 → Admitted immediately (holds a permit while executing)
Requests 8–10 → Queued (OldestFirst), waiting for a permit to be released
Request 11+ → Rejected with HTTP 429
As soon as any of the 7 active handlers complete and release their permit, the oldest waiting request in the queue is admitted. If all 3 queue slots are full and another request arrives, it is rejected with status 429 without waiting.
Use Cases
| Endpoint Type | Why Concurrency Limiting Helps |
|---|
| Database-heavy read/write operations | Prevents connection pool exhaustion under burst load |
| Outbound calls to external third-party APIs | Respects upstream connection limits and avoids cascading failures |
| Expensive in-memory compute (e.g., PDF gen) | Keeps CPU and memory usage within predictable bounds |
| Endpoints that hold distributed locks | Reduces lock contention and deadlock probability under high traffic |
Combine with a timeout policy for best resilience. A request queued by the concurrency limiter will wait indefinitely for a permit unless you also enforce a request timeout. In ASP.NET Core, you can use app.UseRequestTimeouts() (available from .NET 8 onwards) alongside the concurrency limiter so that requests stuck in the queue for too long are cancelled gracefully rather than holding resources open. This two-layer approach — cap concurrency and cap wait time — gives you the strongest protection for expensive downstream calls.