Skip to content

Commit

Permalink
Add RetryResults and RetryResult
Browse files Browse the repository at this point in the history
Adds result objects that hold exceptions, timing and other information related to each of the retries.
  • Loading branch information
Max Young committed Nov 3, 2017
1 parent 387508e commit f8c204b
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 49 deletions.
2 changes: 1 addition & 1 deletion Mulligan.Tests/Mulligan.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="UnitTest1.cs" />
<Compile Include="While.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
14 changes: 0 additions & 14 deletions Mulligan.Tests/UnitTest1.cs

This file was deleted.

41 changes: 41 additions & 0 deletions Mulligan.Tests/While.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Mulligan.Tests
{
[TestClass]
public class While
{
[TestMethod]
public void RetryWhileFalse()
{
int index = 0;
List<int> list = new List<int>() {1, 2, 3};

Func<int> func = () =>
{
try
{
return list[index];
}
finally
{
index++;
}
};

Predicate<int> predicate = @int => @int != 3;

RetryResults<int> results = Retry.While(predicate, func, TimeSpan.FromSeconds(1));

Assert.AreEqual(3, results.GetResult());
Assert.AreEqual(3, results.GetRetryCount());
Assert.IsTrue(results.SuccessResult.Success);
Assert.IsTrue(results.FailureResults.All(f => !f.Success));
Assert.IsTrue(results.Retries.All(r => r.Exception is null));
}
}
}
24 changes: 24 additions & 0 deletions Mulligan/Models/RetryResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Mulligan.Models
{
public sealed class RetryResult<TResult> : RetryResult
{
public TResult Result { get; set; }
}

public class RetryResult
{
public DateTime Start { get; set; }

public DateTime Finish { get; set; }

public TimeSpan Duration => Finish.Subtract(Start);

public Exception Exception { get; set; }

public bool Success { get; set; }
}
}
33 changes: 33 additions & 0 deletions Mulligan/Models/RetryResults.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Mulligan.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Mulligan
{
public sealed class RetryResults<TResult> : RetryResults
{
public TResult GetResult() => Retries.Last().Result;

public override int GetRetryCount() => Retries.Count();

public new List<RetryResult<TResult>> FailureResults => Retries.Where(r => r.Success == false).ToList();

public new RetryResult<TResult> SuccessResult => Retries.Single(r => r.Success);

public new List<RetryResult<TResult>> Retries { get; private set; } = new List<RetryResult<TResult>>();
}

public class RetryResults
{
public TimeSpan GetDuration => new TimeSpan(Retries.Sum(r => r.Duration.Ticks));

public virtual int GetRetryCount() => Retries.Count();

public List<RetryResult> FailureResults => Retries.Where(r => r.Success == false).ToList();

public RetryResult SuccessResult => Retries.Single(r => r.Success);

public List<RetryResult> Retries { get; private set; } = new List<RetryResult>();
}
}
140 changes: 106 additions & 34 deletions Mulligan/Retry.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Mulligan.Models;
using System;
using System.Threading;

namespace Mulligan
Expand All @@ -14,22 +15,43 @@ public class Retry
/// <param name="action">Action that will be retried</param>
/// <param name="timeout">Time the action will be retried</param>
/// <param name="retryInterval">Interval between retries</param>
public static void While(Action action, TimeSpan timeout, TimeSpan? retryInterval = null)
public static RetryResults While(Action action, TimeSpan timeout, TimeSpan? retryInterval = null)
{
DateTime startTime = DateTime.Now;
DateTime start = DateTime.Now;
RetryResults results = new RetryResults();

while (true)
{
DateTime retryStart = DateTime.Now;

try
{
action();
return;

results.Retries.Add(new RetryResult()
{
Start = retryStart,
Finish = DateTime.Now,
Success = true
});

return results;
}
catch (Exception exception)
{
if (IsTimedOut(startTime, timeout))
throw new TimeoutException("Timeout occured while retrying", exception);
results.Retries.Add(new RetryResult()
{
Start = retryStart,
Finish = DateTime.Now,
Exception = exception,
Success = false
});

if (IsTimedOut(start, timeout))
return results;
}
finally
{
Thread.Sleep(retryInterval ?? DefaultRetryInterval);
}
}
Expand All @@ -38,97 +60,147 @@ public static void While(Action action, TimeSpan timeout, TimeSpan? retryInterva
/// <summary>
/// Retries a function until the function succeeds or until timeout is reached.
/// </summary>
/// <typeparam name="T">Return type of the function</typeparam>
/// <typeparam name="TResult">Return type of the function</typeparam>
/// <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>
/// <returns>Return of the function</returns>
public static T While<T>(Func<T> function, TimeSpan timeout, TimeSpan? retryInterval = null)
public static RetryResults<TResult> While<TResult>(Func<TResult> function, TimeSpan timeout, TimeSpan? retryInterval = null)
{
DateTime startTime = DateTime.Now;
DateTime start = DateTime.Now;
RetryResults<TResult> results = new RetryResults<TResult>();

while (true)
{
DateTime retryStart = DateTime.Now;

try
{
return function();
results.Retries.Add(new RetryResult<TResult>()
{
Start = retryStart,
Finish = DateTime.Now,
Result = function(),
Success = true
});

return results;
}
catch (Exception exception)
{
Thread.Sleep(retryInterval ?? DefaultRetryInterval);
results.Retries.Add(new RetryResult<TResult>()
{
Start = retryStart,
Finish = DateTime.Now,
Exception = exception,
Success = true
});

if (IsTimedOut(startTime, timeout))
throw new TimeoutException("Timeout occured while retrying", exception);
if (IsTimedOut(start, timeout))
return results;
}
finally
{
Thread.Sleep(retryInterval ?? DefaultRetryInterval);
}
}
}

/// <summary>
/// Retries a function until the predicate evaluates false and the function succeeds or until timeout is reached.
/// </summary>
/// <typeparam name="T">Return type of the function</typeparam>
/// <typeparam name="TResult">Return type of the function</typeparam>
/// <param name="predicate">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>
/// <returns>Return of the function</returns>
public static T While<T>(Predicate<T> predicate, Func<T> function, TimeSpan timeout, TimeSpan? retryInterval = null)
public static RetryResults<TResult> While<TResult>(Predicate<TResult> predicate, Func<TResult> function, TimeSpan timeout, TimeSpan? retryInterval = null)
{
DateTime startTime = DateTime.Now;
DateTime start = DateTime.Now;
RetryResults<TResult> results = new RetryResults<TResult>();

while (true)
{
DateTime retryStart = DateTime.Now;

try
{
T result = function();
TResult result = function();

results.Retries.Add(new RetryResult<TResult>()
{
Start = retryStart,
Finish = DateTime.Now,
Result = result,
Success = true
});

if (!predicate(result))
return result;
return results;

if (IsTimedOut(startTime, timeout))
return result;
if (IsTimedOut(start, timeout))
return results;
}
catch (Exception exception)
{
Thread.Sleep(retryInterval ?? DefaultRetryInterval);
results.Retries.Add(new RetryResult<TResult>()
{
Start = retryStart,
Finish = DateTime.Now,
Exception = exception,
Success = false
});

if (IsTimedOut(startTime, timeout))
throw new TimeoutException("Timeout occured while retrying", exception);
if (IsTimedOut(start, timeout))
return results;
}
finally
{
Thread.Sleep(retryInterval ?? DefaultRetryInterval);
}
}
}

/// <summary>
/// Retries a function until the predicate evaluates false and the function succeeds or until timeout is reached.
/// </summary>
/// <typeparam name="T">Return type of the function</typeparam>
/// <typeparam name="TResult">Return type of the function</typeparam>
/// <param name="predicate">Predicate that evaluates the results of the function</param>
/// <param name="function">Function that will be retried</param>
/// <param name="tryCatchHandler">Custom exception handling for function</param>
/// <param name="timeout">Time the action will be retried</param>
/// <param name="retryInterval">Interval between retries</param>
/// <returns>Return of the function</returns>
public static T While<T>(Predicate<T> predicate, Func<T> function, Func<Func<T>, T> tryCatchHandler, TimeSpan timeout, TimeSpan? retryInterval = null)
public static RetryResults<TResult> While<TResult>(Predicate<TResult> predicate, Func<TResult> function, Func<Func<TResult>, RetryResult<TResult>> tryCatchHandler, TimeSpan timeout, TimeSpan? retryInterval = null)
{
DateTime startTime = DateTime.Now;
DateTime start = DateTime.Now;
RetryResults<TResult> results = new RetryResults<TResult>();

while (true)
{
T result = tryCatchHandler(function);
if (!predicate(result))
return result;
DateTime retryStart = DateTime.Now;

if (IsTimedOut(startTime, timeout))
return result;
RetryResult<TResult> result = tryCatchHandler(function);

results.Retries.Add(result);

if (!predicate(result.Result))
return results;

if (IsTimedOut(start, timeout))
return results;

Thread.Sleep(retryInterval ?? DefaultRetryInterval);
}
}

private static bool IsTimedOut(DateTime startTime, TimeSpan timeout)
{
// Check for infinite timeout
if (timeout.TotalMilliseconds < 0)
{
if (timeout == TimeSpan.MaxValue)
return false;
}

return DateTime.Now.Subtract(startTime) >= timeout;
}
}
Expand Down

0 comments on commit f8c204b

Please sign in to comment.