Created
May 1, 2012 21:02
-
-
Save xpando/2571365 to your computer and use it in GitHub Desktop.
C# fluent retries
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Retry | |
{ | |
int _times; | |
Func<int, TimeSpan> _delay = i => TimeSpan.FromTicks(0); | |
List<Predicate<Exception>> _shouldRetry = new List<Predicate<Exception>>(); | |
Action<int, Exception> _log = (i, ex) => { Debug.WriteLine("Quartet.Core.Retry: {0}", ex.ToString()); }; | |
Retry(int times) | |
{ | |
_times = times; | |
} | |
/// <summary> | |
/// Tries to invoke code a maximum number of times before rethrowing excpetions | |
/// </summary> | |
/// <param name="times">The maximum number of times to call the lambda passed to Do</param> | |
/// <returns>The retry context</returns> | |
public static Retry Times(int times) | |
{ | |
return new Retry(times); | |
} | |
/// <summary> | |
/// Delays the specified amount of time between tries | |
/// </summary> | |
/// <param name="delay">The amount of time to wait between tries</param> | |
/// <returns>The retry context</returns> | |
public Retry Delay(TimeSpan delay) | |
{ | |
_delay = i => delay; | |
return this; | |
} | |
/// <summary> | |
/// Delay for a computed amount of time between tries. This overload is usefull for implemeting backoff strategies | |
/// </summary> | |
/// <param name="delay"></param> | |
/// <returns>The retry context</returns> | |
public Retry Delay(Func<int, TimeSpan> delay) | |
{ | |
_delay = delay; | |
return this; | |
} | |
/// <summary> | |
/// Only retries if the specified type of exception is thrown | |
/// </summary> | |
/// <typeparam name="TException">The type of exception to retry on</typeparam> | |
/// <returns>The retry context</returns> | |
public Retry When<TException>() | |
{ | |
_shouldRetry.Add(ex => typeof(TException).IsAssignableFrom(ex.GetType())); | |
return this; | |
} | |
/// <summary> | |
/// Only retries when the specified predicate matches the exception | |
/// </summary> | |
/// <typeparam name="TException">The type of exception</typeparam> | |
/// <param name="predicate">A function that evaluates the exception</param> | |
/// <returns>The retry context</returns> | |
public Retry When<TException>(Predicate<TException> predicate) where TException : Exception | |
{ | |
_shouldRetry.Add(ex => typeof(TException).IsAssignableFrom(ex.GetType()) ? predicate(ex as TException) : false); | |
return this; | |
} | |
/// <summary> | |
/// Allows processing of exceptions that ocur during retry execution | |
/// </summary> | |
/// <param name="log">An action that receives the current try and the exception that was thrown</param> | |
/// <returns>The retry context</returns> | |
public Retry Log(Action<int, Exception> log) | |
{ | |
_log = log; | |
return this; | |
} | |
/// <summary> | |
/// The action to retry. | |
/// </summary> | |
/// <returns>The retry context</returns> | |
public TResult Do<TResult>(Func<int, TResult> action) | |
{ | |
for (var i = 1; i <= _times; i++) | |
{ | |
try | |
{ | |
return action(i); | |
} | |
catch (Exception ex) | |
{ | |
var retry = | |
i < _times && // not the last try | |
(_shouldRetry.Count == 0 || // no excpetion filters | |
_shouldRetry.Any(p => p(ex))); // any exception filters vote yes | |
if (retry) | |
{ | |
try { _log(i, ex); } catch {} | |
var delay = _delay(i); | |
if (delay.Ticks > 0) | |
Thread.Sleep(delay); | |
} | |
else | |
throw; | |
} | |
} | |
throw new Exception("Should not ever get here!"); | |
} | |
public void Do(Action<int> action) | |
{ | |
Do(i => { action(i); return 0; }); | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[TestClass] | |
public class RetryTests | |
{ | |
class TestException : Exception | |
{ | |
} | |
class OtherException : Exception | |
{ | |
} | |
[TestMethod] | |
public void RetriesTimesSpeciedNumberOfTimes() | |
{ | |
int times = 0; | |
Retry | |
.Times(4) | |
.Do | |
( | |
i => | |
{ | |
times++; | |
switch (i) | |
{ | |
case 1: | |
throw new Exception(); | |
case 2: | |
throw new TestException(); | |
case 3: | |
throw new OtherException(); | |
} | |
} | |
); | |
Assert.AreEqual(4, times); | |
} | |
[TestMethod] | |
public void RetriesWhenExceptionTypeMatches() | |
{ | |
int times = 0; | |
Retry | |
.Times(3) | |
.When<TestException>() | |
.Do | |
( | |
i => | |
{ | |
times++; | |
if (i < 3) | |
throw new TestException(); | |
} | |
); | |
Assert.AreEqual(3, times); | |
} | |
[ExpectedException(typeof(OtherException))] | |
[TestMethod] | |
public void ThrowsWhenExceptionTypeDoesNotMatch() | |
{ | |
Retry | |
.Times(3) | |
.When<TestException>() | |
.Do | |
( | |
_ => | |
{ | |
throw new OtherException(); | |
} | |
); | |
} | |
[TestMethod] | |
public void CanLogExceptions() | |
{ | |
int tries = 0; | |
Exception exception = null; | |
Retry | |
.Times(3) | |
.Log((i,ex) => exception = ex) | |
.Do | |
( | |
i => | |
{ | |
tries++; | |
if (i == 1) | |
throw new OtherException(); | |
} | |
); | |
Assert.IsNotNull(exception); | |
Assert.AreEqual(2, tries); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment