Resilion 1.0.0-pre
dotnet add package Resilion --version 1.0.0-pre
NuGet\Install-Package Resilion -Version 1.0.0-pre
<PackageReference Include="Resilion" Version="1.0.0-pre" />
<PackageVersion Include="Resilion" Version="1.0.0-pre" />
<PackageReference Include="Resilion" />
paket add Resilion --version 1.0.0-pre
#r "nuget: Resilion, 1.0.0-pre"
#:package Resilion@1.0.0-pre
#addin nuget:?package=Resilion&version=1.0.0-pre&prerelease
#tool nuget:?package=Resilion&version=1.0.0-pre&prerelease
Resilion
A modern resilience library for .NET. Retry, circuit breaker, timeout, fallback, rate limiting, hedging, and pipeline composition — with zero external dependencies in the core package.
Free forever. Resilion has no paid tier, no "enterprise edition," and no plans to add one. If it's useful to you, consider buying us a coffee — never a requirement, always appreciated.
Quick Start
using Resilion;
// Create a resilience pipeline with multiple strategies
var pipeline = Pipeline.Create(b => b
.AddTimeout(TimeSpan.FromSeconds(30))
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = RetryDelay.Exponential(TimeSpan.FromSeconds(1)),
})
.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
FailureRatioThreshold = 0.5,
MinimumThroughput = 10,
})
.AddTimeout(TimeSpan.FromSeconds(5)));
// Execute with the pipeline
var result = await pipeline.ExecuteAsync(
static (httpClient, ct) => httpClient.GetStringAsync("https://api.example.com/data", ct),
new HttpClient());
Why Resilion?
Resilion is designed for .NET developers who want powerful resilience patterns without complexity:
Coming from Polly? See docs/migration-from-polly.md for a concept map and before/after code samples for the five most common patterns, or docs/comparison-with-polly.md for an honest side-by-side on where each library is stronger today.
Zero Dependencies in Core
The Resilion package has zero external dependencies. Everything you need for production resilience is built-in.
Simple, Fluent API
One Pipeline.Create() call. Chain strategies. Build. That's it. No framework to learn, no builder patterns, no magic.
var pipeline = Pipeline.Create(b => b
.AddRetry(options)
.AddCircuitBreaker(options)
.AddTimeout(duration));
Sync and Async
Both Execute and ExecuteAsync with true sync implementations — not sync-over-async. No blocking calls, no artificial overhead.
// Synchronous execution
var result = pipeline.Execute(state => DoWork(state), httpClient);
// Asynchronous execution
var result = await pipeline.ExecuteAsync(async (state, ct) =>
await DoWorkAsync(state, ct), httpClient);
Outcome-Based Resilience
No PredicateBuilder or PredicateResult chains. Just Func<Outcome<T>, bool>:
var pipeline = Pipeline.Create<HttpResponseMessage>(b => b.AddRetry(
new RetryStrategyOptions<HttpResponseMessage>
{
MaxRetryAttempts = 3,
ShouldHandle = outcome =>
outcome.Exception is HttpRequestException // Handle exceptions
|| (outcome.TryGetResult(out var r) && (int)r.StatusCode >= 500), // or result-based
}));
Simple Callbacks
Assign callbacks directly. No ValueTask wrapping for synchronous logging:
new RetryStrategyOptions
{
OnRetry = (context) =>
{
logger.LogWarning($"Retry attempt {context.AttemptNumber}");
}
}
Composable Pipelines
Combine pre-built pipelines into larger ones:
var basePipeline = Pipeline.Create(b => b
.AddRetry(retryOptions)
.AddCircuitBreaker(cbOptions));
var fullPipeline = Pipeline.Create(b => b
.AddRateLimiter(rlOptions)
.AddPipeline(basePipeline));
Allocation-Conscious Design
Outcome<T>is a structResilienceContextis pooled- State parameters avoid closures
- Designed for performance-critical paths
Installation
Core Package
dotnet add package Resilion
With Dependency Injection & Telemetry
dotnet add package Resilion.Extensions
Rate Limiting Strategy
dotnet add package Resilion.RateLimiting
Packages & Dependencies
| Package | Purpose | Dependencies |
|---|---|---|
| Resilion | Core library with all built-in strategies | None |
| Resilion.Extensions | DI registration, telemetry (Meter/ActivitySource), structured logging | Microsoft.Extensions.* |
| Resilion.RateLimiting | Rate limiting strategy with multiple algorithms | System.Threading.RateLimiting |
Strategies
Retry
Automatically retry failed operations with customizable delay strategies.
var pipeline = Pipeline.Create(b => b.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = RetryDelay.Exponential(TimeSpan.FromSeconds(1)),
UseJitter = true, // Decorrelated jitter (on by default)
}));
Delay Strategies:
Exponential— Exponential backoff: 1s, 2s, 4s, 8s, ...Linear— Linear backoff: 1s, 2s, 3s, 4s, ...Constant— Fixed delay between retriesCustom— Supply your own delay function
Result-Based Retry on typed pipelines:
var pipeline = Pipeline.Create<HttpResponseMessage>(b => b.AddRetry(
new RetryStrategyOptions<HttpResponseMessage>
{
MaxRetryAttempts = 3,
Delay = RetryDelay.Exponential(TimeSpan.FromSeconds(1)),
ShouldHandle = outcome =>
outcome.Exception is HttpRequestException
|| (outcome.TryGetResult(out var r) && (int)r.StatusCode >= 500),
}));
Use when:
- Calling unreliable remote services
- Handling transient failures (network glitches, temporary outages)
- Need to respect rate limits with backoff
Timeout
Enforce operation timeouts with cooperative cancellation.
var pipeline = Pipeline.Create(b => b.AddTimeout(TimeSpan.FromSeconds(10)));
Key Points:
- Uses
CancellationToken— the operation must observe it - Perfect for async I/O operations
- Can be stacked (total timeout + per-attempt timeout)
Use when:
- Preventing indefinite hangs on remote calls
- Enforcing SLA boundaries
- Protecting against slow endpoints
Circuit Breaker
Prevent cascading failures by stopping requests when failure rates are too high.
var pipeline = Pipeline.Create(b => b.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
FailureRatioThreshold = 0.5, // Trip at 50% failure rate
MinimumThroughput = 10, // Need 10+ calls before evaluating
SamplingDuration = TimeSpan.FromSeconds(30),
BreakDuration = TimeSpan.FromSeconds(30),
}));
States:
- Closed — Normal operation, requests pass through
- Open — Too many failures, requests rejected immediately
- Half-Open — Testing if service has recovered
Use when:
- Protecting against cascading failures
- Integrating with dependent services
- Need fast-fail when a service is down
Fallback
Provide a fallback value or action when operations fail.
var pipeline = Pipeline.Create<string>(b => b.AddFallback(
new FallbackStrategyOptions<string>
{
FallbackAction = "default-value", // Static value
}));
// Or with a function:
var pipeline = Pipeline.Create<string>(b => b.AddFallback(
new FallbackStrategyOptions<string>
{
FallbackAction = (context) => GetCachedValue() ?? "default",
}));
// Or async:
var pipeline = Pipeline.Create<string>(b => b.AddFallback(
new FallbackStrategyOptions<string>
{
FallbackActionAsync = async (context, ct) =>
await GetCachedValueAsync(ct) ?? "default",
}));
Use when:
- You have a sensible default to return
- Calling a secondary data source
- Providing degraded service instead of failure
Rate Limiting
Control request throughput to prevent overload.
using Resilion.RateLimiting;
using System.Threading.RateLimiting;
var limiter = new ConcurrencyLimiter(new ConcurrencyLimiterOptions
{
PermitLimit = 10,
QueueLimit = 0, // Reject excess requests
});
var pipeline = Pipeline.Create(b => b.AddRateLimiter(
new RateLimiterStrategyOptions { RateLimiter = limiter }));
Built-in Limiters:
ConcurrencyLimiter— Limit concurrent operationsTokenBucketRateLimiter— Token bucket algorithmSlidingWindowRateLimiter— Sliding window rate limiting- Custom implementations via
System.Threading.RateLimiting
Use when:
- Limiting load on a resource
- Protecting downstream services
- Controlling API consumption
Hedging
Send duplicate requests if the first one is slow, returning the fastest response.
var pipeline = Pipeline.Create<string>(b => b.AddHedging(
new HedgingStrategyOptions<string>
{
MaxHedgedAttempts = 3,
HedgingDelay = TimeSpan.FromSeconds(2),
}));
Hedging Delay:
TimeSpan.Zero— Fire all requests in parallelTimeSpan.FromSeconds(2)— Wait 2 seconds before sending next requestSystem.Threading.Timeout.InfiniteTimeSpan— Sequential requests (no hedging, just fallback)
Use when:
- Reducing tail latency in latency-sensitive systems
- You can afford duplicate requests
- Calling idempotent endpoints
Strategy Ordering (Canonical Pipeline)
Strategies execute outermost to innermost. The recommended order is:
Pipeline.Create(b => b
.AddRateLimiter(...) // 1. Shed load FIRST
.AddTimeout(TimeSpan.FromSeconds(30)) // 2. Total timeout across all retries
.AddRetry(...) // 3. Retry failures
.AddCircuitBreaker(...) // 4. Track per-attempt success/failure
.AddTimeout(TimeSpan.FromSeconds(5))); // 5. Per-attempt timeout
Why this order?
- Rate Limit prevents the system from being overwhelmed
- Outer Timeout sets a hard boundary for the entire operation
- Retry gives transient failures a chance to succeed
- Circuit Breaker protects downstream services and fast-fails when they're down
- Inner Timeout per-request prevents individual attempts from hanging
Different use cases may require different orderings — this is the safe default.
Dependency Injection
Register pipelines with Microsoft.Extensions.DependencyInjection:
using Resilion.Extensions;
services.AddResiliencePipeline("http-api", b => b
.AddRetry(new RetryStrategyOptions { MaxRetryAttempts = 3 })
.AddTimeout(TimeSpan.FromSeconds(10)));
// Later, resolve — inject IPipelineProvider<string> rather than the full registry if you
// only need to retrieve pipelines, not register new ones:
var provider = serviceProvider.GetRequiredService<IPipelineProvider<string>>();
var pipeline = provider.GetPipeline("http-api");
var result = await pipeline.ExecuteAsync(
async (client, ct) => await client.GetStringAsync(url, ct),
httpClient);
Telemetry
Resilion emits metrics on a "Resilion" Meter — retry attempts, circuit breaker state
changes, timeout expirations, fallback activations, hedging attempts, and rate limiter
rejections — with zero overhead until something subscribes. See
docs/telemetry.md for the full instrument list and how to subscribe with
MeterListener, dotnet-counters, or OpenTelemetry.
Structured logging is also available through callbacks on strategy options.
Supported .NET Versions
- .NET 8.0+
Resilion is built for modern .NET with full support for:
- Top-level statements
- Records and nullable reference types
- Async/await patterns
- Source generators (future features)
Project Structure
src/
Resilion/ Core library (zero dependencies)
Resilion.Extensions/ DI, telemetry, structured logging
Resilion.RateLimiting/ Rate limiting strategy
tests/
Resilion.Tests/ Core strategy and pipeline tests
Resilion.Extensions.Tests/ DI and telemetry tests
benchmarks/
Resilion.Benchmarks/ Performance benchmarks (BenchmarkDotNet)
samples/
Resilion.Samples/ Real-world usage examples
docs/
*.md Feature and architecture documentation
Performance
Resilion is designed for high-performance, latency-sensitive scenarios:
- Struct-based
Outcome<T>avoids allocating on the result path - Pooled
ResilienceContextfor request-scoped state ResilienceContextPooltrades ~14ns of bookkeeping for eliminating a 72-byte allocation per context- Benchmarks included (see
benchmarks/folder)
Benchmark summary
Measured on an Apple M4 Pro against Polly.Core 8.5.2, same pipeline shapes both sides (full results):
| Scenario | Resilion | Polly.Core | Notes |
|---|---|---|---|
| Empty pipeline | 69 ns / 96 B | 59 ns / 0 B | Resilion allocates per-strategy closures; Polly's happy path is allocation-free |
| Single retry (happy path) | 114 ns / 192 B | 165 ns / 0 B | Resilion faster in wall-clock time despite allocating |
| Composite (Timeout+Retry+CB+Timeout) | 393 ns / 976 B | 734 ns / 0 B | The realistic multi-strategy shape most apps run |
| Same pipeline, sync execution | 61 ns / 192 B | — | True sync — no Task machinery |
Resilion is consistently faster wall-clock, at the cost of small per-call allocations from the middleware chain's closures (tracked in future-plans.md item #4). If your workload does any real I/O per call — the overwhelmingly common case — this difference is unlikely to be your bottleneck; if you're pushing hundreds of thousands of allocation-sensitive calls/sec with no I/O, benchmark your own shape before choosing.
Contributing
See CONTRIBUTING.md for guidelines on:
- Code style and standards
- Testing requirements
- PR expectations
- Building and running tests locally
License
Built for developers who care about reliability.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- No dependencies.
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Resilion:
| Package | Downloads |
|---|---|
|
Resilion.Extensions
DI, logging, and OpenTelemetry integration for Resilion. |
|
|
Resilion.RateLimiting
Rate limiting strategy for Resilion wrapping System.Threading.RateLimiting. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-pre | 29 | 9/2/2026 |