Created
December 15, 2023 20:08
-
-
Save jpdillingham/bd433b8841b6e28cf037c53199254f05 to your computer and use it in GitHub Desktop.
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
namespace Navix | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.Threading; | |
using System.Threading.Tasks; | |
/// <summary> | |
/// Retry logic. Stolen from one of my side projects. | |
/// </summary> | |
public static class Retry | |
{ | |
/// <summary> | |
/// Executes logic with the specified retry parameters. | |
/// </summary> | |
/// <param name="task">The logic to execute.</param> | |
/// <param name="isRetryable">A function returning a value indicating whether the last Exception is retryable.</param> | |
/// <param name="onFailure">An action to execute on failure.</param> | |
/// <param name="maxAttempts">The maximum number of retry attempts.</param> | |
/// <param name="maxDelayInMilliseconds">The maximum delay in milliseconds.</param> | |
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> | |
/// <returns>The execution context.</returns> | |
public static async Task Do(Func<Task> task, Func<int, Exception, bool> isRetryable = null, Action<int, Exception> onFailure = null, int maxAttempts = 3, int maxDelayInMilliseconds = int.MaxValue, CancellationToken cancellationToken = default) | |
{ | |
await Do<object>(async () => | |
{ | |
await task(); | |
return Task.FromResult<object>(null); | |
}, isRetryable, onFailure, maxAttempts, maxDelayInMilliseconds, cancellationToken); | |
} | |
/// <summary> | |
/// Executes logic with the specified retry parameters. | |
/// </summary> | |
/// <param name="task">The logic to execute.</param> | |
/// <param name="isRetryable">A function returning a value indicating whether the last Exception is retryable.</param> | |
/// <param name="onFailure">An action to execute on failure.</param> | |
/// <param name="maxAttempts">The maximum number of retry attempts.</param> | |
/// <param name="maxDelayInMilliseconds">The maximum delay in miliseconds.</param> | |
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> | |
/// <typeparam name="T">The Type of the logic return value.</typeparam> | |
/// <returns>The execution context.</returns> | |
public static async Task<T> Do<T>(Func<Task<T>> task, Func<int, Exception, bool> isRetryable = null, Action<int, Exception> onFailure = null, int maxAttempts = 3, int maxDelayInMilliseconds = int.MaxValue, CancellationToken cancellationToken = default) | |
{ | |
isRetryable ??= (_, _) => true; | |
onFailure ??= (_, _) => { }; | |
var exceptions = new List<Exception>(); | |
for (int attempts = 0; attempts < maxAttempts; attempts++) | |
{ | |
if (cancellationToken.IsCancellationRequested) | |
{ | |
throw new OperationCanceledException(); | |
} | |
try | |
{ | |
if (attempts > 0) | |
{ | |
var (delay, jitter) = ExponentialBackoffDelay(attempts, maxDelayInMilliseconds); | |
await Task.Delay(delay + jitter, cancellationToken); | |
} | |
return await task(); | |
} | |
catch (Exception ex) | |
{ | |
exceptions.Add(ex); | |
try | |
{ | |
onFailure(attempts + 1, ex); | |
if (!isRetryable(attempts + 1, ex)) | |
{ | |
break; | |
} | |
} | |
catch (Exception retryEx) | |
{ | |
throw new RetryException($"Failed to retry operation: {retryEx.Message}", ex); | |
} | |
} | |
} | |
throw new AggregateException(exceptions); | |
} | |
public static (int Delay, int Jitter) ExponentialBackoffDelay(int iteration, int maxDelayInMilliseconds = int.MaxValue) | |
{ | |
iteration = Math.Min(100, iteration); | |
var computedDelay = Math.Floor((Math.Pow(2, iteration) - 1) / 2) * 1000; | |
var clampedDelay = (int)Math.Min(computedDelay, maxDelayInMilliseconds); | |
var jitter = new Random().Next(1000); | |
return (clampedDelay, jitter); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment