Zaiets.SmartRetry 1.0.0

dotnet add package Zaiets.SmartRetry --version 1.0.0
                    
NuGet\Install-Package Zaiets.SmartRetry -Version 1.0.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="Zaiets.SmartRetry" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Zaiets.SmartRetry" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Zaiets.SmartRetry" />
                    
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 Zaiets.SmartRetry --version 1.0.0
                    
#r "nuget: Zaiets.SmartRetry, 1.0.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 Zaiets.SmartRetry@1.0.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=Zaiets.SmartRetry&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Zaiets.SmartRetry&version=1.0.0
                    
Install as a Cake Tool

Zaiets.SmartRetry

Production retry library for .NET 10 — exponential backoff, jitter, circuit breaker, and rate-limit awareness in a single, zero-dependency package.

NuGet License: MIT


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 designCircuitBreaker uses fine-grained locking; BackoffCalculator is stateless; [ThreadStatic] Random avoids contention.
  • Allocation-consciousRetryContext is 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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