ZeroAlloc.Resilience 3.3.0

dotnet add package ZeroAlloc.Resilience --version 3.3.0
                    
NuGet\Install-Package ZeroAlloc.Resilience -Version 3.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ZeroAlloc.Resilience" Version="3.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ZeroAlloc.Resilience" Version="3.3.0" />
                    
Directory.Packages.props
<PackageReference Include="ZeroAlloc.Resilience" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ZeroAlloc.Resilience --version 3.3.0
                    
#r "nuget: ZeroAlloc.Resilience, 3.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ZeroAlloc.Resilience@3.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ZeroAlloc.Resilience&version=3.3.0
                    
Install as a Cake Addin
#tool nuget:?package=ZeroAlloc.Resilience&version=3.3.0
                    
Install as a Cake Tool

ZeroAlloc.Resilience

NuGet Build License: MIT AOT GitHub Sponsors

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
Result-aware retry RetryWhen retries the failed Results you call transient; DelayHint honours Retry-After, capped by MaxDelayMs
Retry attempt number [RetryAttempt] on an int? or int parameter passes the retry number to the inner call, for a retry-count header
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 the implementation, a {Service}ResiliencePolicies singleton, and the proxy

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
NonThrowing bool false Asserts a ResilienceError Result return type
RetryWhen string? null Static bool M(E error): retry the failed Results it returns true for; a transient one is a breaker failure, any other a success
RetryOnException string? null Static bool M(Exception exception): false stops the retries
RethrowDeclined bool false Rethrow an exception RetryOnException declines unchanged, instead of wrapping it in ResilienceException
DelayHint string? null Static TimeSpan? M(E error) and/or TimeSpan? M(Exception exception): the next wait, without jitter
MaxDelayMs int no cap Longest wait between attempts, backoff or hint

[RetryAttempt] on an int? or int parameter of a method under [Retry] passes the retry number instead of the caller's argument: null, 1, 2, … for int?, and 0, 1, 2, … for int.

[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>>, also Result and Result<T, ResilienceError> A failure of that type returned — no throw
ValueTask<Result<T, E>> / Task<Result<T, E>>, other E The inner call's own Result returned; see Result Return Types
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
ZR0003 Error Policy cannot build a failure for this Result<T, E> error type
ZR0004 Error Invalid policy attribute value
ZR0006 Warning Policy not applied to method
ZR0007 Error Interface shape not supported by the resilience generator
ZR0008 Error Invalid ZeroAllocGeneratedAccessibility value
ZR0009 Error Retry member not found or signature mismatch
ZR0010 Error / Warning Result-aware retry cannot apply to method
ZR0011 Warning [RetryAttempt] has no effect without [Retry]
ZR0012 Error [RetryAttempt] parameter type not supported
ZR0013 Warning RethrowDeclined has no effect

Documentation

Full docs live in docs/:


License

MIT

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (10)

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.

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.

AI.Sentinel

Security monitoring middleware for IChatClient — prompt injection, hallucination, and operational anomaly detection with an intervention engine.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.3.0 1,759 9/27/2026
3.2.0 2,106 9/26/2026
3.1.0 2,924 9/25/2026
3.0.0 55 9/25/2026
2.0.1 743 9/25/2026
2.0.0 602 9/24/2026
1.3.8 65 9/24/2026
1.3.7 51 9/24/2026
1.3.6 97 9/24/2026
1.3.5 54 9/24/2026
1.3.4 1,440 9/20/2026
1.3.3 217 9/20/2026
1.3.2 5,226 9/19/2026
1.3.1 3,513 8/12/2026
1.3.0 6,148 5/13/2026
1.2.2 253 5/12/2026
1.2.1 345 5/3/2026
1.2.0 175 5/1/2026
1.1.3 114 5/1/2026
1.1.2 119 4/29/2026
Loading failed