Retry patterns with exponential backoff and jitter. Polly v8 retry strategies, transient fault handling, and idempotency considerations. Trigger: retry, exponential backoff, jitter, transient fault, retry policy.
From dotnet-ai-kitnpx claudepluginhub faysilalshareef/dotnet-ai-kit --plugin dotnet-ai-kitThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Executes pre-written implementation plans: critically reviews, follows bite-sized steps exactly, runs verifications, tracks progress with checkpoints, uses git worktrees, stops on blockers.
builder.Services.AddHttpClient("OrdersApi", client =>
{
client.BaseAddress = new Uri("https://api.orders.{Company}.com");
})
.AddResilienceHandler("retry-pipeline", pipeline =>
{
pipeline.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(500),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true, // Adds randomness to prevent thundering herd
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.HandleResult(r =>
r.StatusCode == HttpStatusCode.TooManyRequests ||
r.StatusCode == HttpStatusCode.ServiceUnavailable ||
r.StatusCode >= HttpStatusCode.InternalServerError)
.Handle<HttpRequestException>()
.Handle<TimeoutRejectedException>()
});
});
pipeline.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
DelayGenerator = args =>
{
// Respect Retry-After header from server
if (args.Outcome.Result?.Headers.RetryAfter is { } retryAfter)
{
var delay = retryAfter.Delta
?? (retryAfter.Date.HasValue
? retryAfter.Date.Value - DateTimeOffset.UtcNow
: TimeSpan.FromSeconds(1));
return ValueTask.FromResult<TimeSpan?>(delay);
}
// Default exponential backoff
return ValueTask.FromResult<TimeSpan?>(
TimeSpan.FromSeconds(Math.Pow(2, args.AttemptNumber)));
},
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.HandleResult(r =>
r.StatusCode == HttpStatusCode.TooManyRequests ||
r.StatusCode >= HttpStatusCode.InternalServerError)
});
// EF Core built-in retry
options.UseSqlServer(connectionString, sql =>
{
sql.EnableRetryOnFailure(
maxRetryCount: 3,
maxRetryDelay: TimeSpan.FromSeconds(5),
errorNumbersToAdd: null); // null = retry all transient errors
});
// Custom retry with Polly for non-EF operations
builder.Services.AddResiliencePipeline("db-operation", pipeline =>
{
pipeline.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(200),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
ShouldHandle = new PredicateBuilder()
.Handle<SqlException>(ex => ex.IsTransient)
.Handle<TimeoutException>()
});
});
pipeline.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(500),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
OnRetry = args =>
{
var logger = args.Context.Properties.GetValue(
new ResiliencePropertyKey<ILogger>("logger"), null!);
logger?.LogWarning(
"Retry attempt {AttemptNumber} after {Delay}ms. " +
"Reason: {Reason}",
args.AttemptNumber,
args.RetryDelay.TotalMilliseconds,
args.Outcome.Exception?.Message ??
args.Outcome.Result?.StatusCode.ToString());
return ValueTask.CompletedTask;
}
});
builder.Services.AddResiliencePipeline("message-queue", pipeline =>
{
pipeline.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 5,
Delay = TimeSpan.FromSeconds(1),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
MaxDelay = TimeSpan.FromSeconds(30), // cap max delay
ShouldHandle = new PredicateBuilder()
.Handle<MessageQueueException>()
.Handle<TimeoutException>()
});
});
// Usage
public sealed class MessagePublisher(
[FromKeyedServices("message-queue")] ResiliencePipeline pipeline,
IMessageBus messageBus)
{
public async Task PublishAsync<T>(T message, CancellationToken ct)
{
await pipeline.ExecuteAsync(
async token => await messageBus.SendAsync(message, token),
ct);
}
}
// For POST/PUT operations that create resources
public sealed class PaymentClient(HttpClient httpClient)
{
public async Task<PaymentResult> ChargeAsync(
ChargeRequest request, CancellationToken ct)
{
// Include idempotency key so retries don't duplicate charges
var idempotencyKey = Guid.NewGuid().ToString();
var httpRequest = new HttpRequestMessage(
HttpMethod.Post, "/charges")
{
Content = JsonContent.Create(request)
};
httpRequest.Headers.Add(
"Idempotency-Key", idempotencyKey);
var response = await httpClient.SendAsync(httpRequest, ct);
response.EnsureSuccessStatusCode();
return await response.Content
.ReadFromJsonAsync<PaymentResult>(ct)
?? throw new InvalidOperationException(
"Empty response from payment service");
}
}
Constant: [1s] [1s] [1s] [1s]
Linear: [1s] [2s] [3s] [4s]
Exponential: [1s] [2s] [4s] [8s]
Exp + Jitter: [0.8s] [2.3s] [3.7s] [8.1s] ← Recommended
AddRetry or RetryStrategyOptions usageWaitAndRetryAsync (migration candidate)EnableRetryOnFailure in EF Core configurationwhile / for with try-catch)Idempotency-Key header usage| Scenario | Max Retries | Base Delay | Backoff |
|---|---|---|---|
| HTTP API call | 3 | 500ms | Exponential + jitter |
| Database connection | 3 | 200ms | Exponential + jitter |
| Message queue publish | 5 | 1s | Exponential + jitter |
| File upload | 2 | 1s | Exponential |
| Payment processing | 1-2 | 1s | Exponential (with idempotency key) |