SteadyFlow.Resilience — a lightweight, async-first resilience toolkit for .NET. Includes retry policies with configurable backoff (exponential, linear, jitter, Fibonacci), circuit breaker, rate limiting (token bucket and sliding window), batch processing, metrics and observability hooks, and ASP.NET Core middleware integration — all with 100% test coverage.
$ dotnet add package SteadyFlow.Resilience✨ Lightweight resilience toolkit for .NET
Retry policies · Circuit Breaker · Rate limiting (Token Bucket & Sliding Window) · Batch processing · ASP.NET Core Middleware · Metrics & Observability · Configurable Backoff Strategies
UseResiliencePipeline()IMetricsObserver / LoggingMetricsObserver for complete visibility.WithRetryAsync().WithCircuitBreakerAsync().WithSlidingWindowAsync().WithTokenBucketAsync()dotnet add package SteadyFlow.Resilience
using SteadyFlow.Resilience.Retry;
using SteadyFlow.Resilience.Backoff;
var strategy = new JitterBackoffStrategy();
var retry = new RetryPolicy(maxRetries: 5, initialDelayMs: 200, strategy: strategy);
Func<Task<string>> unreliableAction = async () =>
{
if (new Random().Next(2) == 0)
throw new Exception("Transient failure");
return "Success!";
};
var pipeline = unreliableAction.WithRetryAsync(retry);
var result = await pipeline();
Console.WriteLine(result);
using SteadyFlow.Resilience.Policies;
var breaker = new CircuitBreakerPolicy(failureThreshold: 2, openDuration: TimeSpan.FromSeconds(10));
Func<Task> riskyAction = async () =>
{
if (new Random().Next(3) == 0)
throw new Exception("Boom!");
await Task.CompletedTask;
};
var pipeline = riskyAction.WithCircuitBreakerAsync(breaker);
await pipeline();
using SteadyFlow.Resilience.RateLimiting;
var limiter = new TokenBucketRateLimiter(capacity: 5, refillRatePerSecond: 2);
for (int i = 0; i < 10; i++)
{
await limiter.WaitForAvailabilityAsync();
Console.WriteLine($"Request {i} at {DateTime.Now:HH:mm:ss.fff}");
}
var limiter = new SlidingWindowRateLimiter(maxRequests: 3, window: TimeSpan.FromSeconds(10));
for (int i = 0; i < 6; i++)
{
await limiter.WaitForAvailabilityAsync();
Console.WriteLine($"Request {i} at {DateTime.UtcNow:HH:mm:ss.fff}");
}
using SteadyFlow.Resilience.Extensions;
using SteadyFlow.Resilience.Policies;
using SteadyFlow.Resilience.RateLimiting;
using SteadyFlow.Resilience.Retry;
var limiter = new TokenBucketRateLimiter(capacity: 2, refillRatePerSecond: 1);
var retry = new RetryPolicy(maxRetries: 3, initialDelayMs: 100);
var breaker = new CircuitBreakerPolicy(failureThreshold: 2, openDuration: TimeSpan.FromSeconds(10));
Func<Task> action = async () =>
{
if (new Random().Next(2) == 0)
throw new Exception("Simulated transient failure");
await Task.CompletedTask;
};
var pipeline = action
.WithTokenBucketAsync(limiter)
.WithRetryAsync(retry)
.WithCircuitBreakerAsync(breaker);
await pipeline();
using Microsoft.Extensions.Logging;
using SteadyFlow.Resilience.Metrics;
ILoggerFactory factory = LoggerFactory.Create(b => b.AddConsole());
var observer = new LoggingMetricsObserver(factory.CreateLogger("resilience"));
observer.OnRetry(1, new Exception("Transient error"));
observer.OnCircuitOpened();
observer.OnRateLimited("TokenBucket");
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseResiliencePipeline(options =>
{
options.Retry = new RetryPolicy(maxRetries: 3);
options.CircuitBreaker = new CircuitBreakerPolicy(failureThreshold: 5, openDuration: TimeSpan.FromSeconds(30));
options.SlidingWindowLimiter = new SlidingWindowRateLimiter(maxRequests: 100, window: TimeSpan.FromMinutes(1));
});
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
IMetricsObserverRun locally:
dotnet test
Contributions are welcome! Please see CONTRIBUTING.md for details.
If you find SteadyFlow.Resilience useful, please give it a ⭐ on GitHub — it helps others discover the project!
Licensed under the MIT License – see LICENSE for details.