private static async Task <TResult> WithFuncAsyncCore <TTuple, TResult>(TaskFuncFactory <TTuple, TResult> factory, Action <TransientOperationOptions> setup) where TTuple : Template { var options = setup.ConfigureOptions(); if (!options.EnableRecovery) { return(await factory.ExecuteMethodAsync().ContinueWithSuppressedContext()); } DateTime timestamp = DateTime.UtcNow; TimeSpan latency = TimeSpan.Zero; TimeSpan totalWaitTime = TimeSpan.Zero; TimeSpan lastWaitTime = TimeSpan.Zero; bool isTransientFault = false; bool throwExceptions; List <Exception> aggregatedExceptions = new List <Exception>(); TResult result = default(TResult); for (int attempts = 0; ;) { bool exceptionThrown = false; TimeSpan waitTime = options.RetryStrategy(attempts); try { if (latency > options.MaximumAllowedLatency) { throw new LatencyException(string.Format(CultureInfo.InvariantCulture, "The latency of the operation exceeded the allowed maximum value of {0} seconds. Actual latency was: {1} seconds.", options.MaximumAllowedLatency.TotalSeconds, latency.TotalSeconds)); } return(await factory.ExecuteMethodAsync().ContinueWithSuppressedContext()); } catch (Exception ex) { try { lock (aggregatedExceptions) { aggregatedExceptions.Insert(0, ex); } isTransientFault = options.DetectionStrategy(ex); if (attempts >= options.RetryAttempts) { throw; } if (!isTransientFault) { throw; } lastWaitTime = waitTime; totalWaitTime = totalWaitTime.Add(waitTime); attempts++; await Task.Delay(waitTime).ContinueWithSuppressedContext(); latency = DateTime.UtcNow.Subtract(timestamp).Subtract(totalWaitTime); } catch (Exception) { throwExceptions = true; exceptionThrown = true; if (isTransientFault) { var evidence = new TransientFaultEvidence(attempts, lastWaitTime, totalWaitTime, latency, new MethodDescriptor(factory.DelegateInfo).ToString()); aggregatedExceptions.InsertTransientFaultException(evidence); FaultCallback?.Invoke(evidence); } break; } } finally { if (exceptionThrown) { IDisposable disposable = result as IDisposable; disposable?.Dispose(); } } } if (throwExceptions) { throw new AggregateException(aggregatedExceptions); } return(result); }
private static void WithActionCore <TTuple>(ActionFactory <TTuple> factory, Action <TransientOperationOptions> setup = null) where TTuple : Template { var options = setup.ConfigureOptions(); if (!options.EnableRecovery) { factory.ExecuteMethod(); return; } DateTime timestamp = DateTime.UtcNow; TimeSpan latency = TimeSpan.Zero; TimeSpan totalWaitTime = TimeSpan.Zero; TimeSpan lastWaitTime = TimeSpan.Zero; bool isTransientFault = false; bool throwExceptions; List <Exception> aggregatedExceptions = new List <Exception>(); for (int attempts = 0; ;) { TimeSpan waitTime = options.RetryStrategy(attempts); try { if (latency > options.MaximumAllowedLatency) { throw new LatencyException(string.Format(CultureInfo.InvariantCulture, "The latency of the operation exceeded the allowed maximum value of {0} seconds. Actual latency was: {1} seconds.", options.MaximumAllowedLatency.TotalSeconds, latency.TotalSeconds)); } factory.ExecuteMethod(); return; } catch (Exception ex) { try { lock (aggregatedExceptions) { aggregatedExceptions.Insert(0, ex); } isTransientFault = options.DetectionStrategy(ex); if (attempts >= options.RetryAttempts) { throw; } if (!isTransientFault) { throw; } lastWaitTime = waitTime; totalWaitTime = totalWaitTime.Add(waitTime); attempts++; Sleep(waitTime); latency = DateTime.UtcNow.Subtract(timestamp).Subtract(totalWaitTime); } catch (Exception) { throwExceptions = true; if (isTransientFault) { var evidence = new TransientFaultEvidence(attempts, lastWaitTime, totalWaitTime, latency, new MethodDescriptor(factory.DelegateInfo).ToString()); aggregatedExceptions.InsertTransientFaultException(evidence); FaultCallback?.Invoke(evidence); } break; } } } if (throwExceptions) { throw new AggregateException(aggregatedExceptions); } }
private static void InsertTransientFaultException(this IList <Exception> aggregatedExceptions, TransientFaultEvidence evidence) { TransientFaultException transientException = new TransientFaultException("The amount of retry attempts has been reached.", evidence); lock (aggregatedExceptions) { aggregatedExceptions.Insert(0, transientException); } }
/// <summary> /// Initializes a new instance of the <see cref="TransientFaultException"/> class. /// </summary> /// <param name="message">The message that describes the error.</param> /// <param name="innerException">The exception that is the cause of the current exception. If the innerException parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception.</param> /// <param name="evidence">The evidence that provide details about the transient fault.</param> public TransientFaultException(string message, Exception innerException, TransientFaultEvidence evidence) : base(message, innerException) { Validator.ThrowIfNull(evidence, nameof(evidence)); Evidence = evidence; }