Zaiets.SmartRetry
1.0.0
dotnet add package Zaiets.SmartRetry --version 1.0.0
NuGet\Install-Package Zaiets.SmartRetry -Version 1.0.0
<PackageReference Include="Zaiets.SmartRetry" Version="1.0.0" />
<PackageVersion Include="Zaiets.SmartRetry" Version="1.0.0" />
<PackageReference Include="Zaiets.SmartRetry" />
paket add Zaiets.SmartRetry --version 1.0.0
#r "nuget: Zaiets.SmartRetry, 1.0.0"
#:package Zaiets.SmartRetry@1.0.0
#addin nuget:?package=Zaiets.SmartRetry&version=1.0.0
#tool nuget:?package=Zaiets.SmartRetry&version=1.0.0
Zaiets.SmartRetry
Production retry library for .NET 10 — exponential backoff, jitter, circuit breaker, and rate-limit awareness in a single, zero-dependency package.
Installation
dotnet add package Zaiets.SmartRetry
Quick Start
using Zaiets.SmartRetry;
// Retry an HTTP call up to 4 times with exponential backoff + full jitter
var result = await SmartRetry.Policy()
.WithMaxAttempts(4)
.WithExponentialBackoff(TimeSpan.FromSeconds(1))
.WithJitter()
.RetryOn<HttpRequestException>()
.Named("fetch-user")
.Build<string>()
.ExecuteAsync(ct => httpClient.GetStringAsync("/api/users/1", ct), cancellationToken);
API Reference
SmartRetry.Policy()
Returns a RetryPolicyBuilder pre-configured with sensible defaults:
| Default | Value |
|---|---|
| Max attempts | 3 |
| Backoff | Exponential, base 1 s |
| Jitter | Full |
Call SmartRetry.EmptyPolicy() for a blank slate.
Builder Methods
Attempt Limits
.WithMaxAttempts(5) // total calls including the first
Backoff Strategies
.WithConstantDelay(TimeSpan.FromMilliseconds(500))
.WithLinearBackoff(TimeSpan.FromSeconds(1)) // attempt × base
.WithExponentialBackoff(TimeSpan.FromSeconds(1), multiplier: 2.0)
.WithMaxDelay(TimeSpan.FromSeconds(60)) // cap on any computed delay
Jitter
.WithJitter() // JitterMode.Full (default)
.WithJitter(JitterMode.Decorrelated) // great for large fleets
.WithJitter(JitterMode.Equal)
.WithoutJitter()
| Mode | Description |
|---|---|
Full |
Uniform random in [0, delay] |
Decorrelated |
Random in [base, 3 × prev] — avoids correlated retries |
Equal |
delay/2 + rand(0, delay/2) |
None |
Raw computed delay, no randomness |
Exception Filtering
// Retry only specific exception types
.RetryOn<HttpRequestException>()
.RetryOn<TimeoutException>()
// Custom predicate (OR'd with RetryOn<T> types)
.RetryWhen(ex => ex.Message.Contains("transient"))
// Built-in predicates
.RetryWhen(RetryPredicates.TransientHttp)
.RetryWhen(RetryPredicates.TransientDatabase)
.RetryWhen(RetryPredicates.NetworkError)
.RetryWhen(RetryPredicates.Timeout)
Callbacks
.OnRetry(ctx =>
{
logger.LogWarning(
"[{Key}] Attempt {Attempt}/{Max} failed: {Ex}. Retrying in {Delay}ms.",
ctx.OperationKey, ctx.Attempt, ctx.MaxAttempts,
ctx.LastException.Message, ctx.NextDelay.TotalMilliseconds);
})
RetryContext properties: OperationKey, Attempt, MaxAttempts, LastException, NextDelay, StartedAt, ElapsedTime, IsLastRetry, Properties.
Circuit Breaker
var policy = SmartRetry.Policy()
.WithMaxAttempts(3)
.WithExponentialBackoff(TimeSpan.FromSeconds(1))
.WithCircuitBreaker(
failureThreshold: 5,
recoveryTime: TimeSpan.FromSeconds(30),
onStateChanged: (from, to) => logger.LogWarning("Circuit {From} -> {To}", from, to))
.Named("payment-service")
.Build();
try
{
await policy.ExecuteAsync(ct => paymentService.ChargeAsync(order, ct));
}
catch (CircuitBreakerOpenException ex)
{
// Fail fast, return cached result, queue for later, etc.
logger.LogError("Circuit open, recovery at {Time}", ex.RecoveryAt);
}
Shared Circuit Breaker
Share one breaker across multiple policies targeting the same downstream service:
var breaker = SmartRetry.CreateCircuitBreaker("inventory-api", failureThreshold: 3);
var readPolicy = SmartRetry.Policy().WithSharedCircuitBreaker(breaker).Build<Inventory>();
var writePolicy = SmartRetry.Policy().WithSharedCircuitBreaker(breaker).Build();
Rate-Limit Awareness
var policy = SmartRetry.Policy()
.WithMaxAttempts(5)
.RateLimitAware() // honours Retry-After headers
.RetryWhen(RetryPredicates.RateLimited)
.Build<HttpResponseMessage>();
Standalone helper for single-call use:
var response = await RateLimitHandler.ExecuteAsync(
() => httpClient.GetAsync("/api/data"),
new RateLimitOptions { MaxRetryAfter = TimeSpan.FromMinutes(1) },
cancellationToken);
Pre-Built Policies (SmartRetry.Defaults)
// Retries 408, 429, 500, 502, 503, 504 — up to 4 attempts, 30 s cap
var response = await SmartRetry.Defaults.HttpResilience
.ExecuteAsync(ct => httpClient.GetAsync("/api/resource", ct));
// Retries deadlock / timeout / connection errors — 3 attempts, linear 500 ms
await SmartRetry.Defaults.Database
.ExecuteAsync(ct => dbContext.SaveChangesAsync(ct));
// Aggressive constant 50 ms, no jitter — for in-process optimistic-concurrency
await SmartRetry.Defaults.InProcess
.ExecuteAsync(_ => cache.TryUpdateAsync(key, value));
// Rate-limit-aware, decorrelated jitter, up to 5 attempts
var response = await SmartRetry.Defaults.RateLimitAwareHttp
.ExecuteAsync(ct => httpClient.GetAsync("/api/resource", ct));
Metrics
var metrics = new RetryMetrics();
var policy = SmartRetry.Policy()
.OnRetry(metrics.AsRetryCallback())
.Named("checkout")
.Build();
// ... run operations ...
var snapshot = metrics.GetSnapshot("checkout");
Console.WriteLine($"Total retries: {snapshot?.TotalRetries}, Avg delay: {snapshot?.AverageDelayMs:0}ms");
Synchronous API
var policy = SmartRetry.Policy().Build<string>();
string result = policy.Execute(() => SomeSynchronousOperation());
var voidPolicy = SmartRetry.Policy().Build();
voidPolicy.Execute(() => SomeSynchronousVoidOperation());
Design Principles
- Zero dependencies — pure .NET BCL, no third-party packages.
- Thread-safe by design —
CircuitBreakeruses fine-grained locking;BackoffCalculatoris stateless;[ThreadStatic]Randomavoids contention. - Allocation-conscious —
RetryContextis only created on failure paths; the hot success path is allocation-free. - Composable — predicates, callbacks, and circuit breakers are independent concerns combined via the builder.
License
MIT — Copyright (c) 2025 Vladyslav Zaiets | https://sarmkadan.com
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 102 | 5/3/2026 |