From f8c204b818d04244e97d25abacf47926719e3bae Mon Sep 17 00:00:00 2001 From: Max Young Date: Fri, 3 Nov 2017 17:22:08 -0400 Subject: [PATCH] Add RetryResults and RetryResult Adds result objects that hold exceptions, timing and other information related to each of the retries. --- Mulligan.Tests/Mulligan.Tests.csproj | 2 +- Mulligan.Tests/UnitTest1.cs | 14 --- Mulligan.Tests/While.cs | 41 ++++++++ Mulligan/Models/RetryResult.cs | 24 +++++ Mulligan/Models/RetryResults.cs | 33 +++++++ Mulligan/Retry.cs | 140 ++++++++++++++++++++------- 6 files changed, 205 insertions(+), 49 deletions(-) delete mode 100644 Mulligan.Tests/UnitTest1.cs create mode 100644 Mulligan.Tests/While.cs create mode 100644 Mulligan/Models/RetryResult.cs create mode 100644 Mulligan/Models/RetryResults.cs diff --git a/Mulligan.Tests/Mulligan.Tests.csproj b/Mulligan.Tests/Mulligan.Tests.csproj index 7feb4c5..9e9f85f 100644 --- a/Mulligan.Tests/Mulligan.Tests.csproj +++ b/Mulligan.Tests/Mulligan.Tests.csproj @@ -48,7 +48,7 @@ - + diff --git a/Mulligan.Tests/UnitTest1.cs b/Mulligan.Tests/UnitTest1.cs deleted file mode 100644 index 5b1523f..0000000 --- a/Mulligan.Tests/UnitTest1.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Mulligan.Tests -{ - [TestClass] - public class UnitTest1 - { - [TestMethod] - public void TestMethod1() - { - } - } -} diff --git a/Mulligan.Tests/While.cs b/Mulligan.Tests/While.cs new file mode 100644 index 0000000..cb241f1 --- /dev/null +++ b/Mulligan.Tests/While.cs @@ -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 list = new List() {1, 2, 3}; + + Func func = () => + { + try + { + return list[index]; + } + finally + { + index++; + } + }; + + Predicate predicate = @int => @int != 3; + + RetryResults 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)); + } + } +} diff --git a/Mulligan/Models/RetryResult.cs b/Mulligan/Models/RetryResult.cs new file mode 100644 index 0000000..05e9836 --- /dev/null +++ b/Mulligan/Models/RetryResult.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Mulligan.Models +{ + public sealed class RetryResult : 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; } + } +} diff --git a/Mulligan/Models/RetryResults.cs b/Mulligan/Models/RetryResults.cs new file mode 100644 index 0000000..9b29567 --- /dev/null +++ b/Mulligan/Models/RetryResults.cs @@ -0,0 +1,33 @@ +using Mulligan.Models; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Mulligan +{ + public sealed class RetryResults : RetryResults + { + public TResult GetResult() => Retries.Last().Result; + + public override int GetRetryCount() => Retries.Count(); + + public new List> FailureResults => Retries.Where(r => r.Success == false).ToList(); + + public new RetryResult SuccessResult => Retries.Single(r => r.Success); + + public new List> Retries { get; private set; } = new List>(); + } + + public class RetryResults + { + public TimeSpan GetDuration => new TimeSpan(Retries.Sum(r => r.Duration.Ticks)); + + public virtual int GetRetryCount() => Retries.Count(); + + public List FailureResults => Retries.Where(r => r.Success == false).ToList(); + + public RetryResult SuccessResult => Retries.Single(r => r.Success); + + public List Retries { get; private set; } = new List(); + } +} diff --git a/Mulligan/Retry.cs b/Mulligan/Retry.cs index 125a54f..5b516ce 100644 --- a/Mulligan/Retry.cs +++ b/Mulligan/Retry.cs @@ -1,4 +1,5 @@ -using System; +using Mulligan.Models; +using System; using System.Threading; namespace Mulligan @@ -14,22 +15,43 @@ public class Retry /// Action that will be retried /// Time the action will be retried /// Interval between retries - 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); } } @@ -38,27 +60,48 @@ public static void While(Action action, TimeSpan timeout, TimeSpan? retryInterva /// /// Retries a function until the function succeeds or until timeout is reached. /// - /// Return type of the function + /// Return type of the function /// Function that will be retried /// Time the action will be retried /// Interval between retries /// Return of the function - public static T While(Func function, TimeSpan timeout, TimeSpan? retryInterval = null) + public static RetryResults While(Func function, TimeSpan timeout, TimeSpan? retryInterval = null) { - DateTime startTime = DateTime.Now; + DateTime start = DateTime.Now; + RetryResults results = new RetryResults(); while (true) { + DateTime retryStart = DateTime.Now; + try { - return function(); + results.Retries.Add(new RetryResult() + { + Start = retryStart, + Finish = DateTime.Now, + Result = function(), + Success = true + }); + + return results; } catch (Exception exception) { - Thread.Sleep(retryInterval ?? DefaultRetryInterval); + results.Retries.Add(new RetryResult() + { + 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); } } } @@ -66,33 +109,55 @@ public static T While(Func function, TimeSpan timeout, TimeSpan? retryInte /// /// Retries a function until the predicate evaluates false and the function succeeds or until timeout is reached. /// - /// Return type of the function + /// Return type of the function /// Predicate that evaluates the results of the function /// Function that will be retried /// Time the action will be retried /// Interval between retries /// Return of the function - public static T While(Predicate predicate, Func function, TimeSpan timeout, TimeSpan? retryInterval = null) + public static RetryResults While(Predicate predicate, Func function, TimeSpan timeout, TimeSpan? retryInterval = null) { - DateTime startTime = DateTime.Now; + DateTime start = DateTime.Now; + RetryResults results = new RetryResults(); while (true) { + DateTime retryStart = DateTime.Now; + try { - T result = function(); + TResult result = function(); + + results.Retries.Add(new RetryResult() + { + 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() + { + 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); } } } @@ -100,35 +165,42 @@ public static T While(Predicate predicate, Func function, TimeSpan time /// /// Retries a function until the predicate evaluates false and the function succeeds or until timeout is reached. /// - /// Return type of the function + /// Return type of the function /// Predicate that evaluates the results of the function /// Function that will be retried /// Custom exception handling for function /// Time the action will be retried /// Interval between retries /// Return of the function - public static T While(Predicate predicate, Func function, Func, T> tryCatchHandler, TimeSpan timeout, TimeSpan? retryInterval = null) + public static RetryResults While(Predicate predicate, Func function, Func, RetryResult> tryCatchHandler, TimeSpan timeout, TimeSpan? retryInterval = null) { - DateTime startTime = DateTime.Now; + DateTime start = DateTime.Now; + RetryResults results = new RetryResults(); while (true) { - T result = tryCatchHandler(function); - if (!predicate(result)) - return result; + DateTime retryStart = DateTime.Now; - if (IsTimedOut(startTime, timeout)) - return result; + RetryResult 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; } }