ZeroAlloc.Resilience
1.3.0
dotnet add package ZeroAlloc.Resilience --version 1.3.0
NuGet\Install-Package ZeroAlloc.Resilience -Version 1.3.0
<PackageReference Include="ZeroAlloc.Resilience" Version="1.3.0" />
<PackageVersion Include="ZeroAlloc.Resilience" Version="1.3.0" />
<PackageReference Include="ZeroAlloc.Resilience" />
paket add ZeroAlloc.Resilience --version 1.3.0
#r "nuget: ZeroAlloc.Resilience, 1.3.0"
#:package ZeroAlloc.Resilience@1.3.0
#addin nuget:?package=ZeroAlloc.Resilience&version=1.3.0
#tool nuget:?package=ZeroAlloc.Resilience&version=1.3.0
ZeroAlloc.Resilience
Source-generated, zero-allocation resilience policies for .NET.
Add [Retry], [Timeout], [RateLimit], and [CircuitBreaker] to an interface. A Roslyn source generator emits a proxy class that composes all policies in declaration order with no heap allocation on the happy path (beyond the unavoidable CancellationTokenSource for timeout). AOT-safe.
Quick start
dotnet add package ZeroAlloc.Resilience
[Retry(MaxAttempts = 3, BackoffMs = 200, Jitter = true, PerAttemptTimeoutMs = 1_000)]
[Timeout(Ms = 5_000)]
[RateLimit(MaxPerSecond = 100, BurstSize = 10)]
[CircuitBreaker(MaxFailures = 5, ResetMs = 1_000, HalfOpenProbes = 1, Fallback = nameof(FetchFallback))]
public interface IExternalService
{
ValueTask<string> FetchAsync(string id, CancellationToken ct);
ValueTask<string> FetchFallback(string id, CancellationToken ct);
}
// Register — one line wires everything
builder.Services.AddExternalServiceResilience<ExternalServiceImpl>();
Inject IExternalService anywhere — all policies are transparent to the caller.
Performance
Head-to-head vs Polly v8 (the de-facto resilience library in .NET). .NET 10.0.7, BenchmarkDotNet v0.14.0.
| Operation | Polly v8 | ZA.Resilience | Speedup |
|---|---|---|---|
| Retry, happy path | 600 ns / 64 B | 23 ns / 0 B | 26× faster, 0 B alloc |
| CircuitBreaker, closed | 776 ns / 64 B | 17 ns / 0 B | 45× faster, 0 B alloc |
| Retry with 2/3 failures | 22.9 ms / 3,134 B | 27.9 ms / 948 B | 22% slower wall-clock, 3.3× less alloc |
The happy-path gap is driven by Polly's ResiliencePipeline.ExecuteAsync walking the strategy chain via delegate dispatch and allocating a ResilienceContext per call (64 B). ZA emits one direct method per interface — retry/CB checks are inline if statements with no context object or closure.
The retry-with-failures wall-clock gap is dominated by Task.Delay(BackoffMs); the residual 22% is ZA's for-loop scheduling — measurable, but mostly invisible against I/O latency.
Full methodology + self-benchmark: docs/performance.md.
Features
| Feature | Notes |
|---|---|
| Zero allocation on happy path | Policy checks are integer comparisons and CAS operations |
| AOT / trimmer safe | Generated proxy is concrete; no reflection at runtime |
| Retry with exponential backoff | Jitter, per-attempt timeout, total timeout all configurable |
| Timeout | Total operation timeout wrapping all retries and backoff delays |
| Rate limiting | Lock-free token bucket; Shared (singleton) or Instance (per-proxy) scope |
| Circuit breaker | Closed → Open → HalfOpen FSM backed by ZeroAlloc.StateMachine (concurrent CAS) |
| Fallback | Method called when circuit is open — same signature, no allocation |
Result<T> support |
Return Result<T> to get failures without exceptions |
| Method-level overrides | Any attribute on a method shadows the interface-level config for that method |
| DI integration | Generated Add{Service}Resilience<TImpl>() extension registers everything |
Policy execution order
Policies execute in this order on every call:
RateLimit → CircuitBreaker → Timeout → Retry (with PerAttemptTimeout inside)
Each policy runs before the inner call is even attempted. If the rate limiter rejects, the circuit breaker and retry logic are never reached.
Attribute overview
[Retry]
[Retry(MaxAttempts = 3, BackoffMs = 200, Jitter = true, PerAttemptTimeoutMs = 1_000)]
| Property | Type | Default | Description |
|---|---|---|---|
MaxAttempts |
int |
3 |
Total attempts (initial + retries) |
BackoffMs |
int |
200 |
Base backoff ms; actual = BackoffMs * 2^attempt |
Jitter |
bool |
false |
Add up to 50% random jitter to prevent thundering-herd |
PerAttemptTimeoutMs |
int |
0 |
Per-attempt cancellation timeout; 0 = disabled |
[Timeout]
[Timeout(Ms = 5_000)]
| Property | Type | Default | Description |
|---|---|---|---|
Ms |
int |
required | Total operation timeout wrapping all retries and backoff |
[RateLimit]
[RateLimit(MaxPerSecond = 100, BurstSize = 10, Scope = RateLimitScope.Shared)]
| Property | Type | Default | Description |
|---|---|---|---|
MaxPerSecond |
int |
required | Token refill rate |
BurstSize |
int |
1 |
Initial and peak token count |
Scope |
RateLimitScope |
Shared |
Shared = singleton per interface; Instance = per proxy |
[CircuitBreaker]
[CircuitBreaker(MaxFailures = 5, ResetMs = 1_000, HalfOpenProbes = 1, Fallback = nameof(FetchFallback))]
| Property | Type | Default | Description |
|---|---|---|---|
MaxFailures |
int |
5 |
Consecutive failures that trip Closed → Open |
ResetMs |
int |
1_000 |
Delay before Open → HalfOpen |
HalfOpenProbes |
int |
1 |
Successes required to close from HalfOpen |
Fallback |
string? |
null |
Fallback method name — called when circuit is Open |
Method-level overrides
Apply attributes directly to methods to override the interface-level config for that method only:
[Retry(MaxAttempts = 3, BackoffMs = 200)]
public interface IExternalService
{
ValueTask<string> FetchAsync(string id, CancellationToken ct); // uses interface-level
[Retry(MaxAttempts = 1)] // POST is not idempotent — one attempt only
ValueTask PostAsync(string data, CancellationToken ct);
}
Method-level attributes shadow interface-level ones entirely for that method — they are not additive.
Failure surface
| Return type | On failure |
|---|---|
ValueTask<T> / Task<T> |
ResilienceException thrown with Policy property |
ValueTask<Result<T>> / Task<Result<T>> |
Result.Failure(...) returned — no throw |
try
{
var result = await service.FetchAsync("id", ct);
}
catch (ResilienceException ex) when (ex.Policy == ResiliencePolicy.CircuitBreaker)
{
// circuit was open and no fallback was configured
}
Diagnostics
| ID | Severity | Description |
|---|---|---|
| ZR0001 | Error | Fallback method not found or signature mismatch |
| ZR0002 | Warning | Timeout configured but method has no CancellationToken |
Documentation
Full docs live in docs/:
- Getting Started
- Attribute Reference
- Source Generator
- Performance
- Core concepts: Retry · Timeout · Rate Limit · Circuit Breaker · Execution Order
- Guides: Fallback · Method-Level Overrides · Result Return Types · DI Registration
License
MIT
| 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 is compatible. 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 is compatible. 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. |
-
net10.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- ZeroAlloc.Collections (>= 0.1.5)
- ZeroAlloc.Pipeline (>= 0.1.6)
- ZeroAlloc.Results (>= 0.1.4)
- ZeroAlloc.StateMachine (>= 1.0.0)
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- ZeroAlloc.Collections (>= 0.1.5)
- ZeroAlloc.Pipeline (>= 0.1.6)
- ZeroAlloc.Results (>= 0.1.4)
- ZeroAlloc.StateMachine (>= 1.0.0)
-
net9.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
- ZeroAlloc.Collections (>= 0.1.5)
- ZeroAlloc.Pipeline (>= 0.1.6)
- ZeroAlloc.Results (>= 0.1.4)
- ZeroAlloc.StateMachine (>= 1.0.0)
NuGet packages (8)
Showing the top 5 NuGet packages that depend on ZeroAlloc.Resilience:
| Package | Downloads |
|---|---|
|
ZeroAlloc.Scheduling
Source-generated zero-allocation background job scheduling for .NET. |
|
|
ZeroAlloc.Outbox
Source-generated transactional outbox for .NET. |
|
|
AI.Sentinel
Security monitoring middleware for IChatClient — prompt injection, hallucination, and operational anomaly detection with an intervention engine. |
|
|
ZeroAlloc.Rest.Resilience
Wires ZeroAlloc.Rest clients through ZeroAlloc.Resilience proxies. Annotate a [ZeroAllocRestClient] interface with [Retry]/[Timeout]/[CircuitBreaker]; call AddRestResilience to register the resilience-wrapped HTTP client. |
|
|
ZeroAlloc.Scheduling.Resilience
ZeroAlloc.Resilience bridge for ZeroAlloc.Scheduling — wraps IJobTypeExecutor implementations in a Resilience-generated proxy. |
GitHub repositories
This package is not used by any popular GitHub repositories.