15 minlesson

Retry Policies with Polly

Retry Policies with Polly

Retry policies automatically re-execute failed operations, giving transient failures a chance to resolve.

Installing Polly

bash
1dotnet add package Polly

Basic Retry

csharp
1using Polly;
2
3// Retry 3 times on HttpRequestException
4var policy = Policy
5 .Handle<HttpRequestException>()
6 .RetryAsync(3);
7
8var result = await policy.ExecuteAsync(async () =>
9{
10 return await httpClient.GetStringAsync(url);
11});

Retry with Callback

Monitor what's happening:

csharp
1var policy = Policy
2 .Handle<HttpRequestException>()
3 .RetryAsync(3, onRetry: (exception, retryCount) =>
4 {
5 Console.WriteLine($"Retry {retryCount} due to: {exception.Message}");
6 });

Wait and Retry

Add delay between retries:

csharp
1// Fixed delay
2var policy = Policy
3 .Handle<HttpRequestException>()
4 .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));
5
6// Variable delays
7var policy = Policy
8 .Handle<HttpRequestException>()
9 .WaitAndRetryAsync(new[]
10 {
11 TimeSpan.FromSeconds(1),
12 TimeSpan.FromSeconds(2),
13 TimeSpan.FromSeconds(4)
14 });
15
16// Calculated delay
17var policy = Policy
18 .Handle<HttpRequestException>()
19 .WaitAndRetryAsync(3, retryAttempt =>
20 TimeSpan.FromSeconds(retryAttempt)); // 1s, 2s, 3s

Handling Multiple Exception Types

csharp
1var policy = Policy
2 .Handle<HttpRequestException>()
3 .Or<TimeoutException>()
4 .Or<SocketException>()
5 .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));

Handling Specific Results

Retry based on response, not just exceptions:

csharp
1var policy = Policy
2 .HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
3 .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));
4
5var response = await policy.ExecuteAsync(() => httpClient.GetAsync(url));

Retry Forever

For critical operations:

csharp
1// With delay to prevent CPU spinning
2var policy = Policy
3 .Handle<HttpRequestException>()
4 .WaitAndRetryForeverAsync(_ => TimeSpan.FromSeconds(5));

Warning: Use with circuit breaker to prevent infinite retries to a dead service.

Context for Logging

Pass context through retries:

csharp
1var policy = Policy
2 .Handle<HttpRequestException>()
3 .WaitAndRetryAsync(
4 3,
5 _ => TimeSpan.FromSeconds(1),
6 onRetry: (exception, timeSpan, retryCount, context) =>
7 {
8 var url = context["url"];
9 Console.WriteLine($"Retry {retryCount} for {url}: {exception.Message}");
10 });
11
12var result = await policy.ExecuteAsync(
13 ctx => httpClient.GetStringAsync(ctx["url"]?.ToString()),
14 new Context { ["url"] = "https://api.example.com" });

Async vs Sync Policies

csharp
1// Async policy (for async operations)
2var asyncPolicy = Policy
3 .Handle<Exception>()
4 .RetryAsync(3);
5
6await asyncPolicy.ExecuteAsync(async () => await DoWorkAsync());
7
8// Sync policy (for sync operations)
9var syncPolicy = Policy
10 .Handle<Exception>()
11 .Retry(3);
12
13syncPolicy.Execute(() => DoWork());

Returning Results

csharp
1var policy = Policy<string>
2 .Handle<HttpRequestException>()
3 .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));
4
5string result = await policy.ExecuteAsync(async () =>
6{
7 return await httpClient.GetStringAsync(url);
8});

Complete Example

csharp
1class ResilientHttpClient
2{
3 private readonly HttpClient _client;
4 private readonly IAsyncPolicy<HttpResponseMessage> _policy;
5
6 public ResilientHttpClient()
7 {
8 _client = new HttpClient();
9
10 _policy = Policy<HttpResponseMessage>
11 .Handle<HttpRequestException>()
12 .OrResult(r => r.StatusCode == HttpStatusCode.ServiceUnavailable)
13 .OrResult(r => r.StatusCode == HttpStatusCode.TooManyRequests)
14 .WaitAndRetryAsync(
15 3,
16 (retryAttempt, response, context) =>
17 {
18 // Check for Retry-After header
19 if (response.Result?.Headers.RetryAfter?.Delta != null)
20 {
21 return response.Result.Headers.RetryAfter.Delta.Value;
22 }
23 return TimeSpan.FromSeconds(Math.Pow(2, retryAttempt));
24 },
25 (response, timeSpan, retryCount, context) =>
26 {
27 Console.WriteLine($"Retry {retryCount} after {timeSpan.TotalSeconds}s");
28 return Task.CompletedTask;
29 });
30 }
31
32 public async Task<string> GetAsync(string url)
33 {
34 var response = await _policy.ExecuteAsync(() => _client.GetAsync(url));
35 response.EnsureSuccessStatusCode();
36 return await response.Content.ReadAsStringAsync();
37 }
38}

Key Takeaways

  • RetryAsync(n) retries n times immediately
  • WaitAndRetryAsync adds delays between retries
  • Handle multiple exception types with Or<T>()
  • Use HandleResult for response-based retry
  • Pass context for logging and debugging
  • Use async policies for async operations
  • Consider exponential backoff (next lesson)