Just built a simple retry engine in C#.
/// <summary>
/// Retry engine.
/// </summary>
public class RetryEngine
{
private static Random rnd = new Random();
private readonly ILogger<RetryEngine> logger;
/// <summary>
/// Creates new retry engine.
/// </summary>
/// <param name="logger">Logger</param>
public RetryEngine(ILogger<RetryEngine> logger)
{
this.logger = logger;
}
/// <summary>
/// Run a task with retry.
/// </summary>
/// <typeparam name="T">Response type.</typeparam>
/// <param name="taskFactory">Task factory.</param>
/// <param name="attempts">Retry times.</param>
/// <param name="when">On error event.</param>
/// <param name="timeOutSeconds">Timeout in seconds.</param>
/// <returns>Response</returns>
public async Task<T> RunWithTry<T>(
Func<int, Task<T>> taskFactory,
int attempts = 3,
Predicate<Exception>? when = null,
int timeOutSeconds = 300)
{
for (var i = 1; i <= attempts; i++)
{
try
{
this.logger.LogTrace($"Starting a job with retry. Attempt: {i}. (Starts from 1)");
var workJob = taskFactory(i);
var waitJob = Task.Delay(TimeSpan.FromSeconds(timeOutSeconds));
await Task.WhenAny(workJob, waitJob);
if (workJob.IsCompleted)
{
return await workJob;
}
else
{
throw new TimeoutException($"Job with cert access has exceeds the {timeOutSeconds} seconds timeout and we have to crash it to trigger another attempt.");
}
}
catch (Exception e)
{
if (when != null)
{
var shouldRetry = when.Invoke(e);
if (!shouldRetry)
{
this.logger.LogTrace(e, $"A task that was asked to retry failed. But from the given condition is false, we gave up retry.");
throw;
}
else
{
this.logger.LogTrace(e, $"A task that was asked to retry failed. With given condition is true.");
}
}
if (i >= attempts)
{
this.logger.LogCritical(e, $"A task that was asked to retry failed. Maximum attempts {attempts} already reached. We have to crash it.");
throw;
}
this.logger.LogInformation(e, $"A task that was asked to retry failed. Current attempt is {i}. maximum attempts is {attempts}. Will retry soon...");
await Task.Delay(ExponentialBackoffTimeSlot(i));
}
}
throw new InvalidOperationException("Code shall not reach here.");
}
/// <summary>
/// Please see <see href="https://en.wikipedia.org/wiki/Exponential_backoff">Exponetial backoff </see> time slot.
/// </summary>
/// <param name="time">the time of trial</param>
/// <returns>Time slot to wait.</returns>
private static TimeSpan ExponentialBackoffTimeSlot(int time)
{
var max = (int)Math.Pow(2, time);
return TimeSpan.FromSeconds(rnd.Next(0, max));
}
}
When you have this, you can do in your business code:
this.retryEngine.RunWithTry(attempt =>
{
return dmsClient.ExecuteManagementCmdlet(cmdletName, parameters);
}, when: e => e is WebException, attempts: 3);
Comments (0)
No comments yet. Be the first to leave one!