Polly Core Rate Limiter

Want to learn more about Polly? Check out my Pluralsight course on it.

Polly, the .NET resiliency framework has had a major update recently. All the same features are there, but some have changed names and the way you use them has changed. One of the changes is the rate limiter. It used to be called Bulkhead isolation.

As I was working on this blog post, I incorrectly used the rate limiter and ended up with an excessive number of calls being executed. This was due to how I was passing the parameters from my loop to the rate limiter.

Here is wrong way to do it -

for (int loop = 1; loop <= 10; loop++)
{
    results.Add(pipeline.ExecuteAsync(async (ct) => await SimpleDelayWithReturnValue(loop, ct)));
}

What happens here is the loop variable is captured by the lambda, and the lambda is executed after the loop has been incremented. So I saw executions of SimpleDelayWithReturnValue where 11 was passed to it. Not what I wanted. One of the big improvements with the newer version of Polly is the reduction in overloaded methods, but I still managed to use the wrong overload in this case.

Thank you to Martin Costello for pointing out my mistake.

This is the right way to do it -

for (int loop = 1; loop <= 10; loop++)
{
    results.Add(pipeline.ExecuteAsync(async (i, ct) => await SimpleDelayWithReturnValue(i, ct), loop));
}

Note, this is not the only way to do it, you could also use a foreach, and this problem would not occur.

To use the rate limiter, you need two NuGet packages - Polly.Core and Polly.RateLimiting.

Here is a fully working example -

 1using System.Threading.RateLimiting;
 2using Polly;
 3using Polly.RateLimiting;
 4
 5var rateLimiter = new RateLimiterStrategyOptions() // setup the rate limiter strategy
 6{
 7    DefaultRateLimiterOptions = new ConcurrencyLimiterOptions()
 8    {
 9        PermitLimit = 5,
10        QueueLimit = 3
11    },
12    OnRejected = (args) =>
13    {   
14        Console.WriteLine($"Rate limit exceeded");
15        return default;
16    }
17};
18
19var pipeline = new ResiliencePipelineBuilder() // add the rate limiter to the pipeline
20    .AddRateLimiter(rateLimiter)
21    .Build();
22
23await TryingRateLimiter(); // call the method to try the rate limiter
24
25async Task TryingRateLimiter()
26{
27    var results = new List<ValueTask<int>>();
28    
29    for (int loop = 1; loop <= 10; loop++)
30    {
31        results.Add(pipeline.ExecuteAsync(async (i, ct) => await SimpleDelayWithReturnValue(i, ct), loop));
32    }
33
34    foreach (var result in results)
35    {
36        try
37        {
38            Console.WriteLine($"Result: {await result}");
39        }
40        catch (RateLimiterRejectedException ex)
41        {
42            Console.WriteLine($"Exception: {ex.Message}");
43        }   
44    }
45}
46
47async ValueTask<int> SimpleDelayWithReturnValue(int number, CancellationToken token) // can return a Task or ValueTask
48{
49    return.WriteLine($"Called SimpleDelay with {number}");
50    await Task.Delay(1000);
51    return number;
52}

That’s it! Have fun with the new Polly!

comments powered by Disqus

Related