Skip to content

Instantly share code, notes, and snippets.

@xpando
Created May 1, 2012 21:02
Show Gist options
  • Save xpando/2571365 to your computer and use it in GitHub Desktop.
Save xpando/2571365 to your computer and use it in GitHub Desktop.
C# fluent retries
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; });
}
}
[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