Robotico.Resilience 1.0.2
Robotico.Resilience
Retry and circuit breaker for .NET 8 and .NET 10. Operations return Robotico.Result for explicit success/error handling. Depends only on Robotico.Result.
Robotico dependencies
flowchart LR A[Robotico.Resilience] --> B[Robotico.Result]
Features
- Retry —
Retry.ExecuteAsync(operation)(single run) orRetry.ExecuteAsync(operation, policy, cancellationToken)withRetryPolicy(max attempts, optional delay). Retries only when the operation returns an error Result. - RetryPolicy —
RetryPolicy.Create(maxAttempts, delayBetweenAttempts). - CircuitBreaker — Tracks consecutive failures; opens after threshold, allows one trial after break duration.
CircuitBreakerOptions.Create(failureThreshold, breakDuration).
Installation
Add the GitHub Packages NuGet source (once per machine) so both packages can be restored:
dotnet nuget add source "https://nuget.pkg.github.com/robotico-dev/index.json" --name github --username YOUR_GITHUB_USERNAME --password YOUR_GITHUB_PAT --store-password-in-clear-text
Then add the packages:
dotnet add package Robotico.Resilience
dotnet add package Robotico.Result
Robotico.Result is published at GitHub Packages (robotico-dev/robotico-results). Robotico.Resilience is published at GitHub Packages (robotico-dev/robotico-resilience-csharp).
Quick start
using Robotico.Resilience;
using VoidResult = Robotico.Result.Result;
// Single execution (no retry)
VoidResult r = await Retry.ExecuteAsync(() => myService.SaveAsync());
// With retry: up to 4 attempts, 100 ms between attempts
RetryPolicy policy = RetryPolicy.Create(maxAttempts: 4, delayBetweenAttempts: TimeSpan.FromMilliseconds(100));
VoidResult r2 = await Retry.ExecuteAsync(() => myService.SaveAsync(), policy);
// Circuit breaker: open after 5 failures, break for 30 s
CircuitBreakerOptions options = CircuitBreakerOptions.Create(failureThreshold: 5, breakDuration: TimeSpan.FromSeconds(30));
CircuitBreaker cb = new CircuitBreaker(options);
VoidResult r3 = await cb.ExecuteAsync(() => myService.SaveAsync());
Configuration (recommended)
Use the factory methods for valid configuration. Object initializers allow invalid values (e.g. FailureThreshold = 0); the constructors will throw. Prefer:
RetryPolicy.Create(maxAttempts, delayBetweenAttempts?, onRetry?)— validatesmaxAttempts >= 1.CircuitBreakerOptions.Create(failureThreshold, breakDuration?, onCircuitStateChange?, clock?)— validatesfailureThreshold >= 1. Optionalclockfor testing (inject a manual clock to avoidTask.Delayin tests).
Thread-safety
- CircuitBreaker — Thread-safe for typical single-instance usage: one
CircuitBreakerper logical circuit (e.g. per downstream service). State is guarded with a lock; concurrent calls toExecuteAsyncare serialized for state updates. Do not share one instance across unrelated operations. - Retry — Stateless; safe to call from multiple threads.
Circuit open error contract
When the circuit is Open, ExecuteAsync does not call the operation and returns a failed Result with an error that has:
- Message:
"Circuit breaker is open." - Code:
"CIRCUIT_OPEN"(so callers can detect and handle open-circuit explicitly). - Severity:
Error.
The error type is Robotico.Result.Errors.SimpleError. Use result.IsError(out IError? err) and inspect err.Code for "CIRCUIT_OPEN" if you need to distinguish open-circuit from other failures.
Testing (injectable time)
To avoid flaky tests that rely on Task.Delay, inject an IClock into CircuitBreakerOptions. In tests, use a manual clock that you advance without real time passing:
// In test project: ManualClock (see Robotico.Resilience.Tests)
var clock = new ManualClock();
var options = CircuitBreakerOptions.Create(failureThreshold: 1, breakDuration: TimeSpan.FromSeconds(10), clock: clock);
var cb = new CircuitBreaker(options);
// ... trigger open ...
clock.Advance(TimeSpan.FromSeconds(15)); // no Task.Delay
var result = await cb.ExecuteAsync(() => Task.FromResult(Result.Success()));
Documentation
Detailed contract and design are in the docs/ folder (AsciiDoc):
- Design (
docs/design.adoc) — Circuit breaker state machine, retry semantics, thread-safety, error contract, versioning
To build HTML from the AsciiDoc sources (e.g. with Asciidoctor):
asciidoctor docs/design.adoc -o docs/design.html
Building and testing
From the repo root:
dotnet restore
dotnet build -c Release
dotnet test tests/Robotico.Resilience.Tests/Robotico.Resilience.Tests.csproj -c Release
To run tests with code coverage (Coverlet; optional gate: fail if line coverage < 90%):
dotnet test tests/Robotico.Resilience.Tests/Robotico.Resilience.Tests.csproj -c Release --collect:"XPlat Code Coverage"
# With threshold (CI): dotnet test ... -c Release --collect:"XPlat Code Coverage" /p:CollectCoverage=true /p:Threshold=90 /p:ThresholdType=line
To run benchmarks (optional; from the repo root; requires building the benchmark project first):
dotnet build benchmarks/Robotico.Resilience.Benchmarks/Robotico.Resilience.Benchmarks.csproj -c Release
dotnet run -c Release -p benchmarks/Robotico.Resilience.Benchmarks/Robotico.Resilience.Benchmarks.csproj -- --filter "*"
If you see an "Ambiguous project name" error when building the benchmark project, build from the repository root (e.g. in a clone of robotico-dev/robotico-resilience-csharp).
Analyzer suppressions (NoWarn)
The library intentionally suppresses the following analyzers; rationale is documented here for principal-grade transparency:
| Code | Rationale |
|---|---|
| RS0026 | Retry.ExecuteAsync has multiple overloads with optional parameters; the analyzer's suggestion would reduce API usability. |
All other warnings are treated as errors (TreatWarningsAsErrors). See Directory.Build.props and .csproj for the full build bar.
Public API: This library uses Microsoft.CodeAnalysis.PublicApiAnalyzers. To populate PublicAPI.Shipped.txt, apply the code fix "Add to PublicAPI.Unshipped" (Fix All in project), then at release move entries to PublicAPI.Shipped.txt.
Dependencies
- Robotico.Result: Success/error results (GitHub Packages)
Versioning
We follow Semantic Versioning. Version 1.0.0 is the first stable release. No breaking changes in minor/patch versions.
License
See repository license file.
Showing the top 20 packages that depend on Robotico.Resilience.
| Packages | Downloads |
|---|---|
|
MishimaWf.Activities
MishimaWf activity host and local dispatch.
|
16 |
|
MishimaWf.Activities
MishimaWf activity host and local dispatch.
|
13 |
|
MishimaWf.Activities
MishimaWf activity host and local dispatch.
|
9 |
|
MishimaWf.Activities
MishimaWf activity host and local dispatch.
|
4 |
|
MishimaWf.Activities
MishimaWf activity host and local dispatch.
|
3 |