Class ErrorRetryExtensions
- Namespace
- Trellis
- Assembly
- Trellis.Core.dll
Transport-neutral retry-classification helpers over the closed Error
catalog. Lets workers, message consumers, and outbound-gateway callers ask "should I
retry this, give up on this item, or halt the batch?" without hand-rolling parallel
switch blocks against framework error types.
public static class ErrorRetryExtensions
- Inheritance
-
ErrorRetryExtensions
- Inherited Members
Remarks
Scope. These helpers answer the generic "retry the same operation later"
question. Domain-specific retry shapes (optimistic-concurrency reload-and-retry for
Error.Conflict; INSERT-with-regenerated-natural-key retry for
SaveChangesWithRetryAsync) require their own primitives because they have to
mutate state before retrying. Consumers writing such loops should switch on the
concrete Error shape directly, not call Classify(Error).
Eventually-consistent reads. Error.NotFound and Error.Gone default to Permanent. Services whose reads are eventually consistent (read-your-own-writes against a replicated store, message consumers ahead of write replication) should override locally rather than expect the framework default to infer their model.
Override pattern. Consumers that disagree with the default mapping for one or more cases should branch on the concrete shape first and only fall back to Classify(Error) for the cases they don't override:
var classification = error switch
{
Error.Conflict c when c.Code == FaultCodes.ConcurrentModification => RetryClassification.Transient,
_ => error.Classify(),
};
Methods
Classify(Error)
Classifies an Error using the framework's default transport-neutral mapping. Use the result to decide whether the surrounding worker / consumer loop should retry this item, give up on it, or halt the batch entirely.
public static RetryClassification Classify(this Error error)
Parameters
errorErrorThe error to classify.
Returns
- RetryClassification
The default RetryClassification for
error.
Remarks
Default mapping (every nested case of Error is enumerated; the catalog is closed so the table is exhaustive at framework-publish time):
| Error case | Classification |
|---|---|
| Error.Unavailable | Transient |
| Error.RateLimited | Transient |
| Error.Unexpected | Transient — see below |
| Error.TransportFault | Permanent — see below |
| Error.AuthenticationRequired | FailFast |
| Error.Forbidden | Permanent |
| Error.InvalidInput | Permanent |
| Error.InvariantViolation | Permanent |
| Error.NotFound | Permanent |
| Error.Gone | Permanent |
| Error.Conflict | Permanent — see below |
| Error.Aggregate | Recursive max-severity: FailFast if any inner classifies as FailFast; otherwise Permanent if any inner classifies as Permanent; otherwise Transient. |
Why Error.Unexpected is Transient. Error.Unexpected wraps unhandled exceptions and other "this should not have happened" situations. A retry can hide a deterministic bug (the same code path will throw again the next time), but it can also recover a transient failure (a momentary serialization error, a stale connection). The default favours availability: producers should reserve Error.Unexpected for unknown internal faults and surface deterministic failures as Error.InvariantViolation, Error.InvalidInput, or Error.Conflict instead. Consumers must still cap retries.
Why Error.TransportFault is Permanent.
ITransportFault is opaque from Trellis.Core's perspective; the
concrete payload is defined by transport-specific packages (for example
HttpError in Trellis.Http.Abstractions). The retryable transient
outcomes those transports produce (HTTP 429, HTTP 503, gRPC UNAVAILABLE) are
mapped at the boundary to Error.RateLimited and
Error.Unavailable — which carry RetryAdvice — and never
reach Error.TransportFault. Every HttpError case shipped today
(405, 406, 412, 413, 415, 416, 428) is a caller-side error that will not succeed by
waiting and retrying. Transport packages that surface their own retryable transient
faults via Error.TransportFault should provide a transport-aware
classification extension that overrides this default for those specific faults.
Why Error.Conflict is Permanent.
Resolving a 409-shaped conflict typically requires conflict-specific work (reload
the current rowversion, re-apply the change, regenerate a colliding natural key)
that a generic outer retry loop cannot perform. Domains that have a meaningful
retry strategy for a conflict should override locally as shown above, or use a
dedicated primitive like DbContext.SaveChangesWithRetryAsync.
Aggregate semantics. The aggregate classification answers "should I retry the operation that produced this aggregate as an indivisible unit?" A mixed aggregate of a permanent and a transient failure is classified Permanent because retrying the unit will not change the permanent inner's outcome. Callers that need to retry the transient parts independently must iterate over Errors and classify each inner separately.
Exceptions
- ArgumentNullException
Thrown when
erroris null.
GetRetryAdvice(Error)
Returns the RetryAdvice carried by the error, when the producer supplied one. Currently only Error.RateLimited and Error.Unavailable carry advice; all other cases (including Error.Aggregate) return null.
public static RetryAdvice? GetRetryAdvice(this Error error)
Parameters
errorErrorThe error to inspect.
Returns
- RetryAdvice?
The producer-supplied RetryAdvice, or null when none was supplied.
Remarks
Aggregate returns null by design. Returning advice from the first transient inner would (a) contradict Classify(Error) when the aggregate classifies as Permanent or FailFast, and (b) silently under-wait when later inners suggest a longer delay. Callers that want a per-inner retry policy should iterate over Errors and apply their own merge rule.
Exceptions
- ArgumentNullException
Thrown when
erroris null.
IsFailFast(Error)
Convenience predicate equivalent to
Classify(Error)(error) == FailFast.
public static bool IsFailFast(this Error error)
Parameters
errorErrorThe error to classify.
Returns
Exceptions
- ArgumentNullException
Thrown when
erroris null.
IsPermanent(Error)
Convenience predicate equivalent to
Classify(Error)(error) == Permanent.
public static bool IsPermanent(this Error error)
Parameters
errorErrorThe error to classify.
Returns
Exceptions
- ArgumentNullException
Thrown when
erroris null.
IsTransient(Error)
Convenience predicate equivalent to
Classify(Error)(error) == Transient.
public static bool IsTransient(this Error error)
Parameters
errorErrorThe error to classify.
Returns
Exceptions
- ArgumentNullException
Thrown when
erroris null.