Skip to content

Transiency Resolvers ​

Transiency classifies exceptions as transient (retryable) or terminal (non-retryable), enabling intelligent retry policies throughout Fusion.

Key Types ​

TypeDescriptionSource
TransiencyException classification resultTransiency.cs
TransiencyResolverDelegate that classifies an exceptionTransiencyResolver.cs
TransiencyResolversBuilt-in resolver implementationsTransiencyResolvers.cs
RetryPolicyConfigurable retry with backoffRetryPolicy.cs

Why Transiency? ​

Not all errors should trigger retries:

Error TypeShould Retry?Example
Network timeoutYesTimeoutException
Connection resetYesSocketException
Server overloadedYesHTTP 503
Invalid argumentNoArgumentException
Not foundNoHTTP 404
Authentication failedNoHTTP 401

TransiencyResolver makes this classification automatic and consistent.

Transiency Enum ​

cs
public enum Transiency
{
    Unknown = 0,     // Treated as NonTransient
    Transient,       // Safe to retry
    SuperTransient,  // Transient error requiring infinite retries
    NonTransient,    // Permanent error — don't retry
    Terminal,        // Fatal error — stop the whole retry chain
}
ValueMeaningTypical Action
UnknownClassification failedTreated as NonTransient
TransientTemporary errorRetry with backoff
SuperTransientVery temporaryRetry indefinitely
NonTransientPermanent errorDon't retry, report error
TerminalFatal errorDon't retry; also stop the enclosing retry chain

TransiencyResolver ​

TransiencyResolver is a delegate:

cs
public delegate Transiency TransiencyResolver(Exception error);

Basic Usage ​

cs
TransiencyResolver resolver = TransiencyResolvers.PreferTransient;

try {
    await DoOperation();
}
catch (Exception ex) {
    var transiency = resolver.Invoke(ex);

    if (transiency.IsAnyTransient()) {
        // Retry the operation
        await Task.Delay(backoff);
        await DoOperation();
    }
    else {
        // Report permanent failure
        throw;
    }
}

Built-in Resolvers ​

The built-in resolvers handle common transient exceptions:

cs
// Built-in transient exceptions (recognized by all resolvers via CoreOnly):
// - ITerminalException → Terminal
// - ISuperTransientException → SuperTransient
// - ITransientException → Transient
// - TimeoutException → Transient
// - RetryPolicyTimeoutException → NonTransient

TransiencyResolver resolver = TransiencyResolvers.PreferTransient;

Custom Resolver ​

cs
// Add custom logic by assigning a lambda
TransiencyResolver customResolver = ex => ex switch {
    MyCustomException e => e.IsRetryable
        ? Transiency.Transient
        : Transiency.Terminal,
    _ => Transiency.Unknown  // Let the fallback decide
};

Chaining Resolvers ​

Resolvers can be chained using the .Or extension — first non-Unknown result wins:

cs
TransiencyResolver resolver = e =>
    (e is MyException me ? me.Transiency : Transiency.Unknown)
    .Or(e, TransiencyResolvers.PreferTransient);

Built-in Resolvers ​

TransiencyResolvers.PreferTransient ​

Assumes unknown exceptions are transient. Used by Fusion's IComputed by default:

cs
var resolver = TransiencyResolvers.PreferTransient;
// Unknown → Transient

TransiencyResolvers.PreferNonTransient ​

Assumes unknown exceptions are non-transient. Used by OperationReprocessor by default:

cs
var resolver = TransiencyResolvers.PreferNonTransient;
// Unknown → NonTransient

TransiencyResolvers.CoreOnly ​

Classifies only the core well-known exception types; returns Unknown for everything else:

cs
var resolver = TransiencyResolvers.CoreOnly;
// Unknown → Unknown (caller decides the fallback)

RetryPolicy ​

RetryPolicy is a record that combines TransiencyResolver with retry logic:

cs
var policy = new RetryPolicy(
    TryCount: 5,
    Delays: RetryDelaySeq.Exp(1, 60)  // 1s → 60s exponential
) {
    TransiencyResolver = TransiencyResolvers.PreferTransient,
    RetryOn = ExceptionFilters.AnyNonTerminal,
};

// Execute with retry
await policy.Apply(async ct => {
    await DoOperationAsync(ct);
}, cancellationToken: cancellationToken);

Retry Delay Sequences ​

cs
// Exponential backoff: 1s, 2s, 4s, 8s, ... up to 60s
var exp = RetryDelaySeq.Exp(
    TimeSpan.FromSeconds(1),
    TimeSpan.FromSeconds(60)
);

// Fixed delay: always 5s
var fixed = RetryDelaySeq.Fixed(TimeSpan.FromSeconds(5));

Usage in Fusion ​

Operations Framework Reprocessing ​

The Operations Framework uses transiency to determine if failed operations should be reprocessed:

cs
// In operation reprocessing
catch (Exception ex) {
    var transiency = transiencyResolver.Invoke(ex);
    if (transiency.IsAnyTransient()) {
        // Mark for reprocessing
        await ScheduleReprocess(operation);
    }
    else {
        // Log permanent failure
        Log.Error(ex, "Operation failed permanently");
    }
}

See Operations Framework Reprocessing for details.

RPC Connection Handling ​

RPC uses transiency for reconnection decisions:

cs
// Connection error handling
catch (Exception ex) {
    var transiency = transiencyResolver.Invoke(ex);
    if (transiency.IsAnyTransient()) {
        // Attempt reconnection with backoff
        await ReconnectWithBackoff();
    }
    else {
        // Connection permanently failed
        await NotifyConnectionLost();
    }
}

DbEntityResolver ​

DbEntityResolver uses a RetryPolicy internally for query retry on transient database errors.

Common Patterns ​

Service Method with Retry ​

cs
public async Task<Data> FetchDataAsync(CancellationToken ct)
{
    var policy = new RetryPolicy(3, RetryDelaySeq.Exp(0.5, 10)) {
        TransiencyResolver = _transiencyResolver,
    };

    return await policy.Apply(async innerCt => {
        return await _httpClient.GetFromJsonAsync<Data>(url, innerCt);
    }, cancellationToken: ct);
}

Custom Exception Classification ​

cs
// Mark your exceptions as transient/terminal
public class RateLimitedException : Exception
{
    public TimeSpan RetryAfter { get; }
}

// Register resolver
services.AddSingleton<TransiencyResolver>(sp => {
    TransiencyResolver custom = ex => ex switch {
        RateLimitedException => Transiency.Transient,
        AuthenticationException => Transiency.Terminal,
        _ => Transiency.Unknown
    };
    return e => custom.Invoke(e).Or(e, TransiencyResolvers.PreferTransient);
});

Logging Transient vs Terminal Errors ​

cs
catch (Exception ex) {
    var transiency = _transiencyResolver.Invoke(ex);

    if (transiency.IsAnyTransient()) {
        _log.LogWarning(ex, "Transient error, will retry");
    }
    else {
        _log.LogError(ex, "Permanent error");
        throw;
    }
}

Best Practices ​

Default to NonTransient for Unknown ​

cs
// Good: Safer default — unknown errors won't trigger unwanted retries
var resolver = TransiencyResolvers.PreferNonTransient;

// Risky: May retry non-retryable operations
var resolver = TransiencyResolvers.PreferTransient;

Classify Your Exceptions ​

cs
// Good: Explicit classification
public class MyServiceException : Exception
{
    public bool IsTransient { get; init; }
}

// Create a resolver as a lambda
TransiencyResolver myResolver = ex =>
    ex is MyServiceException mse
        ? mse.IsTransient ? Transiency.Transient : Transiency.Terminal
        : Transiency.Unknown;

Use Appropriate Backoff ​

cs
// Good: Exponential backoff with reasonable bounds
RetryDelaySeq.Exp(
    TimeSpan.FromMilliseconds(100),  // Start small
    TimeSpan.FromSeconds(30)          // Don't wait too long
)

// Bad: Immediate retry (may overwhelm failing service)
RetryDelaySeq.Fixed(TimeSpan.Zero)