using System; using System.Threading.Tasks; using Polly; using Polly.Timeout; using Serilog; namespace ZB.MOM.WW.LmxProxy.Host.Services { /// /// Provides retry policies for resilient operations /// public static class RetryPolicies { private static readonly ILogger Logger = Log.ForContext(typeof(RetryPolicies)); /// /// Creates a retry policy with exponential backoff for read operations /// public static IAsyncPolicy CreateReadPolicy() { return Policy .Handle(ex => !(ex is ArgumentException || ex is InvalidOperationException)) .WaitAndRetryAsync( 3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt - 1)), (outcome, timespan, retryCount, context) => { Exception? exception = outcome.Exception; Logger.Warning(exception, "Read operation retry {RetryCount} after {DelayMs}ms. Operation: {Operation}", retryCount, timespan.TotalMilliseconds, context.ContainsKey("Operation") ? context["Operation"] : "Unknown"); }); } /// /// Creates a retry policy with exponential backoff for write operations /// public static IAsyncPolicy CreateWritePolicy() { return Policy .Handle(ex => !(ex is ArgumentException || ex is InvalidOperationException)) .WaitAndRetryAsync( 3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (exception, timespan, retryCount, context) => { Logger.Warning(exception, "Write operation retry {RetryCount} after {DelayMs}ms. Operation: {Operation}", retryCount, timespan.TotalMilliseconds, context.ContainsKey("Operation") ? context["Operation"] : "Unknown"); }); } /// /// Creates a retry policy for connection operations with longer delays /// public static IAsyncPolicy CreateConnectionPolicy() { return Policy .Handle() .WaitAndRetryAsync( 5, retryAttempt => { // 2s, 4s, 8s, 16s, 32s var delay = TimeSpan.FromSeconds(Math.Min(32, Math.Pow(2, retryAttempt))); return delay; }, (exception, timespan, retryCount, context) => { Logger.Warning(exception, "Connection retry {RetryCount} after {DelayMs}ms", retryCount, timespan.TotalMilliseconds); }); } /// /// Creates a circuit breaker policy for protecting against repeated failures /// public static IAsyncPolicy CreateCircuitBreakerPolicy() { return Policy .Handle() .CircuitBreakerAsync( 5, TimeSpan.FromSeconds(30), (result, timespan) => { Logger.Error(result.Exception, "Circuit breaker opened for {BreakDurationSeconds}s due to repeated failures", timespan.TotalSeconds); }, () => { Logger.Information("Circuit breaker reset - resuming normal operations"); }, () => { Logger.Information("Circuit breaker half-open - testing operation"); }); } /// /// Creates a combined policy with retry and circuit breaker /// public static IAsyncPolicy CreateCombinedPolicy() { IAsyncPolicy retry = CreateReadPolicy(); IAsyncPolicy circuitBreaker = CreateCircuitBreakerPolicy(); // Wrap retry around circuit breaker // This means retry happens first, and if all retries fail, it counts toward the circuit breaker return Policy.WrapAsync(retry, circuitBreaker); } /// /// Creates a timeout policy for operations /// public static IAsyncPolicy CreateTimeoutPolicy(TimeSpan timeout) { return Policy .TimeoutAsync( timeout, TimeoutStrategy.Pessimistic, async (context, timespan, task) => { Logger.Warning( "Operation timed out after {TimeoutMs}ms. Operation: {Operation}", timespan.TotalMilliseconds, context.ContainsKey("Operation") ? context["Operation"] : "Unknown"); if (task != null) { try { await task; } catch { // Ignore exceptions from the timed-out task } } }); } /// /// Creates a bulkhead policy to limit concurrent operations /// public static IAsyncPolicy CreateBulkheadPolicy(int maxParallelization, int maxQueuingActions = 100) { return Policy .BulkheadAsync( maxParallelization, maxQueuingActions, context => { Logger.Warning( "Bulkhead rejected operation. Max parallelization: {MaxParallel}, Queue: {MaxQueue}", maxParallelization, maxQueuingActions); return Task.CompletedTask; }); } } /// /// Extension methods for applying retry policies /// public static class RetryPolicyExtensions { /// /// Executes an operation with retry policy /// public static async Task ExecuteWithRetryAsync( this IAsyncPolicy policy, Func> operation, string operationName) { var context = new Context { ["Operation"] = operationName }; return await policy.ExecuteAsync(async ctx => await operation(), context); } /// /// Executes an operation with retry policy (non-generic) /// public static async Task ExecuteWithRetryAsync( this IAsyncPolicy policy, Func operation, string operationName) { var context = new Context { ["Operation"] = operationName }; await policy.ExecuteAsync(async ctx => await operation(), context); } } }