OrionResilience 0.6.0

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

<p align="center"> <img src="docs/logo.png" alt="OrionResilience" width="150" /> </p>

OrionResilience

CI/CD NuGet

One opinionated resilience vocabulary for the Orion family: retry with jitter presets and an overall timeout, executed over an OrionClock TimeProvider so every retry fast-forwards in tests, with OpenTelemetry by default and configured through options — not fluent chains.

Backoff is the most re-implemented 40 lines in any backend. Webhook delivery has its own exponential-backoff-with-jitter, lease acquisition has another, and every HttpClient caller writes a for loop with Task.Delay. They disagree on the base delay, the jitter algorithm, the cap, and what counts as retryable — and none of them are testable, because the delay is a real Task.Delay against the real clock, so the retry test either sleeps for real or doesn't exist.

OrionResilience is the opinionated preset layer. Retries and the timeout run on the family's OrionClock, so the same fake clock that fast-forwards leases and TTLs fast-forwards a whole retry sequence — a 4-retry pipeline with a 5-second cap completes in-test in microseconds. Every execution emits the family's orion.* OpenTelemetry signals.

Features

  • Retry + timeout on OrionClock — all delays and the overall timeout run on the clock's TimeProvider. Under FakeOrionClock a retry sequence fast-forwards deterministically; no real waits, no flaky "wait for it" tests.
  • Backoff presetsExponential (configurable factor), DecorrelatedJitter (overflow-safe, injectable sampler for deterministic tests), and Constant, each with an optional cap.
  • Declared retryabilityRetryOn<TException>() and RetryOn(predicate), OR-composed; by default every exception is retryable until you narrow it. Retrying a non-idempotent operation is the caller's declared choice.
  • Typed failuresTimeoutRejectedException (distinct from caller cancellation, which always propagates unchanged) and RetriesExhaustedException (carries the attempt count, wraps the last fault).
  • OpenTelemetry by default — a Moongazing.OrionResilience meter/activity-source carrying orion.resilience.attempts, orion.resilience.retry.delay (ms), and orion.resilience.outcome (tagged with a frozen outcome), plus an execution span. Built on the family's OrionInstrumentation spine, so multi-tenant / multi-region labels stamp every measurement.
  • AOT- and trim-clean, verified by a native-binary smoke test in CI. Multi-targets net8.0, net9.0, net10.0.

Install

dotnet add package OrionResilience

Quick start

using Moongazing.OrionClock;
using Moongazing.OrionResilience;

var clock = new OrionClock(); // the family clock; in DI, resolve IOrionClock / OrionClock

var pipeline = new ResiliencePipeline(clock, new ResiliencePipelineOptions
{
    MaxRetries = 4,                                                  // 1 initial attempt + 4 retries
    Backoff = Backoff.DecorrelatedJitter(
        baseDelay: TimeSpan.FromMilliseconds(200),
        cap: TimeSpan.FromSeconds(5)),
    Timeout = TimeSpan.FromSeconds(10),                             // overall budget for all attempts
}.RetryOn<HttpRequestException>()                                   // only these are retryable
 .RetryOn<TimeoutException>());

HttpResponseMessage response = await pipeline.ExecuteAsync(
    async ct => await httpClient.SendAsync(request, ct),
    cancellationToken);

ExecuteAsync returns the operation's result on success. It throws TimeoutRejectedException when the budget elapses, and RetriesExhaustedException (wrapping the last fault) when every retryable attempt fails. A caller-driven OperationCanceledException always propagates unchanged — it is never reclassified as a timeout.

Testing — retries fast-forward, no real waits

Point the pipeline at FakeOrionClock and advance time by hand. Because the backoff Task.Delay and the timeout CancellationTokenSource are both created through the clock's TimeProvider, advancing the clock fires them — the whole retry sequence runs instantly and deterministically.

using Moongazing.OrionClock.Testing;

var clock = new FakeOrionClock();
var pipeline = new ResiliencePipeline(clock, new ResiliencePipelineOptions
{
    MaxRetries = 4,
    Backoff = Backoff.Exponential(TimeSpan.FromMilliseconds(200), cap: TimeSpan.FromSeconds(5)),
});

var attempts = 0;
var execution = pipeline.ExecuteAsync(async ct =>
{
    if (++attempts < 5) throw new InvalidOperationException("transient");
    await Task.CompletedTask;
    return "ok";
});

// Drive the scheduled backoff waits by advancing the clock, not by sleeping.
while (!execution.IsCompleted) clock.Advance(TimeSpan.FromMilliseconds(500));

Assert.Equal("ok", await execution); // recovered after 4 retries, in microseconds

Pair it with DeterministicFaultInjector (from Orion.Abstractions.Testing) to model a dependency that fails a fixed number of times, or recovers at a known instant, with no randomness.

Observability

Every execution records to a Moongazing.OrionResilience meter and activity source:

Signal Kind Meaning
orion.resilience.attempts counter Individual attempts executed (including the first).
orion.resilience.retry.delay histogram (ms) The backoff waited before each retry, tagged with the 1-based attempt.
orion.resilience.outcome counter Completed executions, tagged outcome = success / failure / timeout / cancelled.

Telemetry emits by default through a shared instance. Hand a ResilienceDiagnostics you own to the pipeline constructor when you want DI-managed lifetime or per-instance scoping.

Roadmap

Wave 1 (this release) ships retry + timeout on OrionClock, the jitter presets, and OpenTelemetry, AOT-clean. Circuit-breaker and hedging, OrionResult-typed errors, options-configured named pipelines (services.AddOrionResilience(...)), and typed-HttpClient integration land in later waves. See CHANGELOG.md.

OrionResilience curates and integrates proven strategies rather than inventing new ones; it is the family's opinionated preset layer, not a new resilience engine, and it is not a rate limiter or bulkhead (those compose from elsewhere in the family).

Versioning

Follows Semantic Versioning. Multi-targets net8.0, net9.0, and net10.0. Binds to Orion.Abstractions 1.x and OrionClock 0.9.x.

Documentation

Contributing

Contributions are welcome. See CONTRIBUTING.md and the CODE_OF_CONDUCT.md.

More from the Orion family

Focused .NET libraries built to one quality bar. Each is usable on its own; several share the small Orion.Abstractions contracts spine, but there is no deep dependency web — pick only what you need:

  • Orion.Abstractions — the shared contracts spine: telemetry, options, result, clock
  • OrionClock — a TimeProvider-based clock with TTL / deadline vocabulary
  • OrionGuard — validation, guard clauses, DDD primitives, domain events
  • OrionAudit — automatic EF Core change-audit trail
  • OrionBeacon — leader election with fencing tokens
  • OrionGrant — permission / authorization checks
  • OrionKey — source-generated strongly-typed IDs
  • OrionLedger — API-key issuance, verification, and rotation
  • OrionLens — ambient correlation-context propagation
  • OrionLock — distributed locks with fencing tokens
  • OrionOnce — idempotency keys for exactly-once request handling
  • OrionPatch — transactional outbox for EF Core
  • OrionRelay — outbound webhook delivery (HMAC, retries, backoff)
  • OrionResult — Result/Option types and a shared error vocabulary
  • OrionSaga — sagas / process managers for long-running workflows
  • OrionShade — sensitive-data redaction for logs and telemetry
  • OrionStream — server-sent events / streaming hub
  • OrionVault — field-level encryption for EF Core

See it all working together in OrionShowcase, a production-shaped banking sample.

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

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
0.6.0 105 8/9/2026