/// <summary> /// Invokes associated handler function for the specified message /// </summary> /// <param name="messageType"></param> /// <param name="messageEnvelope"></param> /// <param name="currentRetryCount"></param> /// <returns></returns> private IHandlerResult PerformHandler(Type messageType, object messageEnvelope, int currentRetryCount) { try { return(this.subscriptions[messageType].Invoke(messageEnvelope)); } catch (Exception exceptionThrown) { const string LogMessageFormat = "Unhandled exception in handler: {0}"; var exceptionToHandle = exceptionThrown is TargetInvocationException && exceptionThrown.InnerException != null ? exceptionThrown.InnerException : exceptionThrown; string exceptionMessage = exceptionToHandle.ToString(); if (RetryResult.WillRetry(currentRetryCount)) { // log a warning for retriable messages, since the error may just be transient this.configuration.Logger.WarnFormat(LogMessageFormat, exceptionMessage); } else { this.configuration.Logger.ErrorFormat(LogMessageFormat, exceptionMessage); } return(new RetryResult(exceptionMessage)); } }
/// <summary> /// Retries a function until the predicate evaluates false and the function succeeds or until timeout is reached. /// </summary> /// <typeparam name="TResult">Return type of the function</typeparam> /// <param name="shouldRetry">Predicate that evaluates the results of the function</param> /// <param name="function">Function that will be retried</param> /// <param name="timeout">Time the action will be retried</param> /// <param name="retryInterval">Interval between retries</param> /// <param name="cancellationToken">Token to cancel retry operation</param> /// <returns>Return of the function</returns> public static RetryResults <TResult> While <TResult>(Predicate <TResult> shouldRetry, Func <TResult> function, TimeSpan timeout, TimeSpan?retryInterval = null, CancellationToken cancellationToken = new CancellationToken()) { DateTime start = DateTime.Now; RetryResults <TResult> results = new RetryResults <TResult>(); while (true) { DateTime retryStart = DateTime.Now; RetryResult <TResult> result = new RetryResult <TResult>(); try { if (cancellationToken.IsCancellationRequested) { cancellationToken.ThrowIfCancellationRequested(); } result.Value = function(); if (!shouldRetry(result.Value)) { result.IsCompletedSuccessfully = true; } } catch (OperationCanceledException canceledException) { result.Exception = canceledException; result.IsCompletedSuccessfully = false; result.IsCanceled = true; } catch (Exception exception) { result.Exception = exception; result.IsCompletedSuccessfully = false; } finally { result.Start = retryStart; result.Finish = DateTime.Now; results.Retries.Add(result); } if (result.IsCompletedSuccessfully) { return(results); } if (result.IsCanceled) { return(results); } if (IsTimedOut(start, timeout)) { return(results); } Thread.Sleep(retryInterval ?? DefaultRetryInterval); } }
public void SuccessfulFunctionDoesntReturnFallbackValue() { // Arrange & Act. RetryResult <int> result = Retrier.Init() .WithMsWaitOf(0) .WithNumberOfRetries(2) .Invoke(() => 10 / 2) .WithFallBackValue(1000); // Assert. Assert.Equal(5, result.Result); Assert.True(result.Successful); Assert.Equal(1, result.RetryInfo.Executions); }
public void CountExceptionsAndRetries() { // Arrange & Act. int zero = 0; RetryResult <int> result = Retrier.Init() .WithMsWaitOf(0) .WithNumberOfRetries(2) .Invoke(() => 2 / zero); // Assert. Assert.Equal(0, result.Result); Assert.False(result.Successful); Assert.Equal(3, result.RetryInfo.Executions); Assert.IsType <DivideByZeroException>(result.RetryInfo.Exceptions.FirstOrDefault()); }
public void ExpireAfterTotalTimeoutWithRetryForever() { // Arrange & Act. int zero = 0; RetryResult <int> result = Retrier.Init() .WithWaitOf(TimeSpan.FromMilliseconds(500)) .RetryUntilSuccessful() .TimeoutAfter(TimeSpan.FromSeconds(1)) .Invoke(() => 2 / zero); // Assert. Assert.Equal(0, result.Result); Assert.False(result.Successful); Assert.Equal(2, result.RetryInfo.Executions); Assert.IsType <DivideByZeroException>(result.RetryInfo.Exceptions.FirstOrDefault()); }
/// <summary> /// Processes an action with this retry policy. /// </summary> /// <param name="policy">The retry policy.</param> /// <param name="action">The async action which will return a result to process.</param> /// <param name="needThrow">A handler to check if need throw the exception without retry.</param> /// <param name="cancellationToken">The optional cancellation token.</param> /// <returns>The processing retry result.</returns> public static async Task <RetryResult <T> > ProcessAsync <T>(this IRetryPolicy policy, Func <CancellationToken, Task <T> > action, Func <Exception, Exception> needThrow, CancellationToken cancellationToken = default) { var result = new RetryResult <T>(); if (action == null) { return(result); } if (needThrow == null) { needThrow = ex => ex; } var retry = policy?.CreateInstance() ?? new InternalRetryInstance(); while (true) { try { cancellationToken.ThrowIfCancellationRequested(); var r = await action(cancellationToken); result.Success(r); return(result); } catch (Exception ex) { result.Fail(ex); ex = needThrow(ex); if (ex != null) { throw ex; } } var span = retry.Next(); if (!span.HasValue) { result.End(); return(result); } await Task.Delay(span.Value, cancellationToken); } }
private void AssertHasException <T>(RetryResult <T> retryResult) { Assert.That(retryResult.HadException, Is.True); Assert.That(retryResult.LastException, Is.Not.Null); }
private void AssertTimedOut <T>(RetryResult <T> retryResult) { Assert.That(retryResult.Success, Is.False); Assert.That(retryResult.TimedOut, Is.True); }
/// <summary> /// Enables retry policy to process. /// </summary> /// <param name="cancellationToken">The optional cancellation token.</param> /// <returns>The processing retry result.</returns> public async Task <RetryResult> ProcessAsync(CancellationToken cancellationToken = default) { State = TaskStates.Initializing; var result = new RetryResult(); var retry = RetryPolicy?.CreateInstance() ?? new InternalRetryInstance(); State = TaskStates.Working; while (true) { try { cancellationToken.ThrowIfCancellationRequested(); } catch (OperationCanceledException) { State = TaskStates.Canceled; throw; } catch (ObjectDisposedException) { State = TaskStates.Canceled; throw; } try { await OnProcessAsync(cancellationToken); Processing?.Invoke(this, new RetryEventArgs(retry.ProcessTime)); State = TaskStates.Done; result.Success(); return(result); } catch (Exception ex) { State = TaskStates.WaitingToRetry; result.Fail(ex); try { ex = ExceptionHandler.GetException(ex); } catch (Exception) { State = TaskStates.Faulted; throw; } if (ex != null) { State = TaskStates.Faulted; throw ex; } } var span = retry.Next(); if (!span.HasValue) { State = TaskStates.Faulted; result.End(); return(result); } await Task.Delay(span.Value, cancellationToken); State = TaskStates.Retrying; } }