Skip to main content
net8.0net9.0net10.0

Source-generated, zero-allocation resilience policies for .NET. Add [Retry], [Timeout], [RateLimit], and [CircuitBreaker] to an interface; the generator emits a proxy composing all policies in declaration order. AOT-safe.

Published
Apr 28, 2026
5mo ago
Total Downloads
48.2K
48,196

Install

dotnet add package ZeroAlloc.Resilience --version 1.1.1
ProductVersions
.NET
net10.0net8.0net9.0

README

ZeroAlloc.Resilience

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.

NuGet Build


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.


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/:


License

MIT

Changes from Previous Version

No structural changes between v1.1.0 and v1.1.1.

Adoption Assessment

Assess this package against its supported frameworks, license, maintenance signals and dependency graph before making it a project standard.

Compatibility metadata lists 3 target frameworks; confirm the exact target used by your application.The published metadata declares 15 direct dependency entries across framework groups. Inspect the resolved dependency graph for your target framework.

Related Guides & Workflows

Learn how to evaluate, integrate, or audit this package in your .NET project.