Last active
August 29, 2015 13:57
-
-
Save ahmetb/9539490 to your computer and use it in GitHub Desktop.
Retry logic modified for our testing needs, see comments at the end
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
using System; | |
using System.Diagnostics; | |
using System.Threading.Tasks; | |
public class RetryLoop<TResult> | |
{ | |
public RetryLoop(Func<RetryIterationContext<TResult>, Task<TResult>> func, Func<RetryIterationContext<TResult>, bool> succeeded) | |
{ | |
this.func = func; | |
this.timer = new Stopwatch(); | |
this.BeforeRetry = r => Task.FromResult(false); | |
this.AfterRetry = r => Task.FromResult(false); | |
this.ShouldRetry = r => true; | |
this.Succeeded = succeeded; | |
} | |
/// <summary> | |
/// Optional. The function will be called between retries before making the retry. | |
/// Can be used to add timing policies between retries. | |
/// </summary> | |
public Func<RetryIterationContext<TResult>, Task> BeforeRetry { get; set; } | |
/// <summary> | |
/// Optional. The function will be called after a retry even though iteration has not | |
/// succeeded. Can be used to capture <see cref="RetryIterationContext{TResult}.Exception"/> | |
/// from the retryable function or <see cref="RetryIterationContext{TResult}.Value"/> produced | |
/// between runs and log various things about those. | |
/// </summary> | |
/// <remarks> | |
/// Exceptions thrown by this are not handled in the retry loop or served in | |
/// <see cref="RetryIterationContext{TResult}.Exception"/>. Handle separately. | |
/// </remarks> | |
public Func<RetryIterationContext<TResult>, Task> AfterRetry { get; set; } | |
/// <summary> | |
/// Optional. This function will be called to determine if function should be retried | |
/// again. Can be used to stop retrying after a condition is met (e.g. timeout). | |
/// If not specified, it will be indefinitely retried until <see cref="Succeeded"/> | |
/// returns <c>true</c>. | |
/// </summary> | |
public Func<RetryIterationContext<TResult>, bool> ShouldRetry { get; set; } | |
/// <summary> | |
/// This function will be called to determine if the retry iteration is | |
/// successful. If returns <c>true</c>, function will not be retried again. | |
/// </summary> | |
public Func<RetryIterationContext<TResult>, bool> Succeeded { get; private set; } | |
private readonly Func<RetryIterationContext<TResult>, Task<TResult>> func; | |
private readonly Stopwatch timer; | |
/// <returns>Context of last iteration that finished.</returns> | |
public async Task<RetryIterationContext<TResult>> ExecuteAsync() | |
{ | |
RetryIterationContext<TResult> iterationContext; | |
bool shouldRetry = false; | |
TimeSpan startTime = this.timer.Elapsed; | |
int iteration = 0; | |
this.timer.Start(); | |
do | |
{ | |
iterationContext = new RetryIterationContext<TResult>(iteration); | |
try | |
{ | |
if (shouldRetry) | |
{ | |
await this.BeforeRetry(iterationContext); | |
} | |
iterationContext.Elapsed = this.timer.Elapsed - startTime; | |
iterationContext.StartDate = DateTime.UtcNow; | |
iterationContext.Value = await this.func(iterationContext); | |
} | |
catch (Exception e) | |
{ | |
iterationContext.Exception = e; | |
} | |
iterationContext.Elapsed = this.timer.Elapsed - startTime; | |
iterationContext.Succeeded = this.Succeeded(iterationContext); | |
await this.AfterRetry(iterationContext); | |
shouldRetry = this.ShouldRetry(iterationContext); | |
iteration++; | |
} while (!iterationContext.Succeeded && shouldRetry); | |
return iterationContext; | |
} | |
} | |
public class RetryIterationContext<TResult> | |
{ | |
public RetryIterationContext(int iteration) | |
{ | |
this.Iteration = iteration; | |
} | |
/// <summary> | |
/// Iteration number of this instance. | |
/// </summary> | |
public int Iteration { get; set; } | |
/// <summary> | |
/// UTC time when retry has started just after <see cref="RetryLoop{TResult}.BeforeRetry"/> is | |
/// executed, if specified. | |
/// </summary> | |
public DateTime StartDate { get; set; } | |
/// <summary> | |
/// UTC time when run has finished and <see cref="RetryLoop{TResult}.AfterRetry"/> has not | |
/// been executed yet, if specified. | |
/// </summary> | |
public DateTime FinishDate | |
{ | |
get { return StartDate + Elapsed; } | |
} | |
/// <summary> | |
/// Time elapsed since retry loop has started. | |
/// </summary> | |
public TimeSpan Elapsed { get; set; } | |
/// <summary> | |
/// Success of the loop on this iteration. | |
/// </summary> | |
public bool Succeeded { get; set; } | |
/// <summary> | |
/// Stores exception, if any, thrown on this iteration | |
/// </summary> | |
public Exception Exception { get; set; } | |
/// <summary> | |
/// Value produced in this iteration, if any. Accessing the value | |
/// if the iteration is not succeeded may result in undefined behavior, | |
/// based on the function passed to the <see cref="RetryLoop{TResult}"/>. | |
/// </summary> | |
public TResult Value { get; set; } | |
} |
Fixes made based on Brian's suggestion.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here is what changed from Brian's original code
Succeeded
func is mandatory (moved to constructor, will nullchk)Value
obtained in that iteration (intermediate retry iterations can produce steps too, and we can use them inShouldRetry
).uint
(was int) and starting from 0 (it was so in Brian's)