Configurable retry logic and circuit breaker for .NET — exponential backoff, jitter, and built-in presets.
dotnet add package Philiprehberger.RetryKitConfigurable retry logic and circuit breaker for .NET — exponential backoff, jitter, and built-in presets.
dotnet add package Philiprehberger.RetryKit
using Philiprehberger.RetryKit;
// Async retry with defaults (3 attempts, exponential backoff)
var result = await Retry.ExecuteAsync(async ct =>
{
var response = await httpClient.GetAsync("/api/data", ct);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync(ct);
});
// Synchronous retry
var value = Retry.Execute(() => SomeOperation());
// Custom options
var result = await Retry.ExecuteAsync(async ct =>
{
return await FetchData(ct);
}, new RetryOptions
{
MaxAttempts = 5,
Backoff = BackoffStrategy.Linear,
InitialDelay = TimeSpan.FromMilliseconds(500),
MaxDelay = TimeSpan.FromSeconds(10),
Jitter = true,
OnRetry = (ex, attempt, delay) => Console.WriteLine($"Retry {attempt}: {ex.Message} (next in {delay.TotalMilliseconds}ms)"),
});
// Use presets
var data = await Retry.ExecuteAsync(ct => FetchData(ct), Presets.Aggressive);
Return a default value instead of throwing when all retries are exhausted:
// Async fallback — returns "default" if all attempts fail
var result = await Retry.ExecuteWithFallbackAsync(
async ct => await FetchData(ct),
fallbackValue: "default",
new RetryOptions { MaxAttempts = 3 }
);
// Synchronous fallback
var value = Retry.ExecuteWithFallback(
() => LoadConfig(),
fallbackValue: new Config { UseDefaults = true }
);
Only retry specific exception types — non-matching exceptions are thrown immediately:
// Only retry transient HTTP errors, fail fast on 4xx client errors
var result = await Retry.ExecuteIfAsync(
async ct =>
{
var response = await httpClient.GetAsync("/api/data", ct);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync(ct);
},
shouldRetry: ex => ex is HttpRequestException { StatusCode: >= System.Net.HttpStatusCode.InternalServerError },
new RetryOptions { MaxAttempts = 3 }
);
// Synchronous version
var value = Retry.ExecuteIf(
() => LoadFromDatabase(),
shouldRetry: ex => ex is TimeoutException or IOException
);
Each attempt gets its own timeout. If an attempt times out, it counts as a failure and triggers retry:
var result = await Retry.ExecuteWithTimeoutAsync(
async ct =>
{
var response = await httpClient.GetAsync("/api/data", ct);
return await response.Content.ReadAsStringAsync(ct);
},
timeout: TimeSpan.FromSeconds(5),
new RetryOptions { MaxAttempts = 3 }
);
The OnRetry callback receives the exception, attempt number, and the computed delay before the next attempt:
var options = new RetryOptions
{
MaxAttempts = 5,
OnRetry = (ex, attempt, delay) =>
Console.WriteLine($"Attempt {attempt} failed: {ex.Message}. Retrying in {delay.TotalMilliseconds}ms..."),
};
var breaker = new CircuitBreaker(
failureThreshold: 5,
resetTimeoutSeconds: 30,
onStateChange: (from, to) => Console.WriteLine($"{from} -> {to}")
);
// Synchronous
var result = breaker.Call(() => SomeOperation());
// Async
var data = await breaker.CallAsync(async () => await FetchData());
// Check metrics
var metrics = breaker.GetMetrics();
Console.WriteLine($"State: {metrics.State}, Successes: {metrics.SuccessCount}, Failures: {metrics.FailureCount}");
| Preset | Attempts | Backoff | Initial Delay | Max Delay |
|---|---|---|---|---|
Aggressive | 5 | Exponential | 500ms | 5s |
Gentle | 3 | Exponential | 2s | 30s |
NetworkRequest | 3 | Exponential | 1s | 10s |
DatabaseQuery | 3 | Linear | 500ms | 5s |
Retry| Method | Description |
|---|---|
ExecuteAsync<T>(fn, options?) | Retry an async operation with configurable backoff |
Execute<T>(fn, options?) | Retry a synchronous operation |
ExecuteWithFallbackAsync<T>(fn, fallback, options?) | Retry with async fallback on exhaustion |
ExecuteWithFallback<T>(fn, fallback, options?) | Retry with synchronous fallback |
RetryOptions| Property | Type | Default | Description |
|---|---|---|---|
MaxAttempts | int | 3 | Maximum retry attempts |
Backoff | BackoffStrategy | Exponential | Backoff strategy |
InitialDelay | TimeSpan | 1s | Initial delay between retries |
MaxDelay | TimeSpan | 30s | Maximum delay cap |
Jitter | bool | true | Add random jitter to delays |
RetryOn | Func<Exception, bool>? | null | Predicate to filter retryable exceptions |
OnRetry | Action<Exception, int, TimeSpan>? | null | Callback on each retry |
CircuitBreaker| Method | Description |
|---|---|
Call<T>(fn) | Execute through the circuit breaker |
CallAsync<T>(fn) | Execute async through the circuit breaker |
GetMetrics() | Get circuit breaker metrics |
State | Current circuit state (Closed, Open, HalfOpen) |
dotnet build src/Philiprehberger.RetryKit.csproj --configuration Release
If you find this project useful: