Last active
August 29, 2018 14:42
-
-
Save jrgcubano/de375db2154f094d04339c09641013eb to your computer and use it in GitHub Desktop.
Retry and circuit breaker pattern in C# (services, httpclient, polly)
This file contains hidden or 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
| https://github.com/App-vNext/Polly/wiki/Circuit-Breaker | |
| CircuitBreakerPolicy breaker = Policy | |
| .Handle<HttpException>() | |
| .CircuitBreaker( | |
| exceptionsAllowedBeforeBreaking: 2, | |
| durationOfBreak: TimeSpan.FromMinutes(1) | |
| ); |
This file contains hidden or 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
| // Via https://alastaircrabtree.com/implementing-the-retry-pattern-for-async-tasks-in-c/ | |
| // Use | |
| var maxRetryAttempts = 3; | |
| var pauseBetweenFailures = TimeSpan.FromSeconds(2); | |
| await RetryHelper.RetryOnExceptionAsync<HttpRequestException> | |
| (maxRetryAttempts, pauseBetweenFailures, async () => { | |
| var response = await httpClient.DeleteAsync( | |
| "https://example.com/api/products/1"); | |
| response.EnsureSuccessStatusCode(); | |
| }); | |
| using System; | |
| using System.Configuration; | |
| using System.Linq; | |
| using System.Reflection; | |
| using System.Threading; | |
| using System.Threading.Tasks; | |
| using log4net; | |
| namespace Utils | |
| { | |
| public static class RetryHelper | |
| { | |
| //I am using log4net but swap in your favourite | |
| private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | |
| public static async Task RetryOnExceptionAsync( | |
| int times, TimeSpan delay, Func<Task> operation) | |
| { | |
| await RetryOnExceptionAsync<Exception>(times, delay, operation); | |
| } | |
| public static async Task RetryOnExceptionAsync<TException>( | |
| int times, TimeSpan delay, Func<Task> operation) where TException : Exception | |
| { | |
| if (times <= 0) | |
| throw new ArgumentOutOfRangeException(nameof(times)); | |
| var attempts = 0; | |
| do | |
| { | |
| try | |
| { | |
| attempts++; | |
| await operation(); | |
| break; | |
| } | |
| catch (TException ex) | |
| { | |
| if (attempts == times) | |
| throw; | |
| await CreateDelayForException(times, attempts, delay, ex); | |
| } | |
| } while (true); | |
| } | |
| private static Task CreateDelayForException( | |
| int times, int attempts, TimeSpan delay, Exception ex) | |
| { | |
| var delay = IncreasingDelayInSeconds(attempts); | |
| Log.Warn($"Exception on attempt {attempts} of {times}. " + | |
| "Will retry after sleeping for {delay}.", ex); | |
| return Task.Delay(delay); | |
| } | |
| internal static int[] DelayPerAttemptInSeconds = | |
| { | |
| (int) TimeSpan.FromSeconds(2).TotalSeconds, | |
| (int) TimeSpan.FromSeconds(30).TotalSeconds, | |
| (int) TimeSpan.FromMinutes(2).TotalSeconds, | |
| (int) TimeSpan.FromMinutes(10).TotalSeconds, | |
| (int) TimeSpan.FromMinutes(30).TotalSeconds | |
| }; | |
| static int IncreasingDelayInSeconds(int failedAttempts) | |
| { | |
| if (failedAttempts <= 0) throw new ArgumentOutOfRangeException(); | |
| return failedAttempts > DelayPerAttemptInSeconds.Length ? DelayPerAttemptInSeconds.Last() : DelayPerAttemptInSeconds[failedAttempts]; | |
| } | |
| } | |
| } |
This file contains hidden or 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
| // Example 1: | |
| var httpClient = new HttpClient(); | |
| var maxRetryAttempts = 3; | |
| var pauseBetweenFailures = TimeSpan.FromSeconds(2); | |
| var retryPolicy = Policy | |
| .Handle<HttpRequestException>() | |
| .WaitAndRetryAsync(maxRetryAttempts, i => pauseBetweenFailures); | |
| await retryPolicy.ExecuteAsync(async () => | |
| { | |
| var response = await httpClient | |
| .DeleteAsync("https://example.com/api/products/1"); | |
| response.EnsureSuccessStatusCode(); | |
| }); | |
| // Example 2: (increase delay on each retry) | |
| Policy | |
| .Handle<HttpRequestException>() | |
| .WaitAndRetryAsync(new[] | |
| { | |
| TimeSpan.FromSeconds(2), | |
| TimeSpan.FromSeconds(4), | |
| TimeSpan.FromSeconds(8) | |
| }); | |
| // Example 3 (calculate the delay at run-time with retry attemp) | |
| Policy | |
| .Handle<HttpRequestException>() | |
| .WaitAndRetryAsync(3, retryAttempt => | |
| TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) | |
| ); |
This file contains hidden or 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
| // Use | |
| var maxRetryAttempts = 3; | |
| var pauseBetweenFailures = TimeSpan.FromSeconds(2); | |
| RetryHelper.RetryOnException(maxRetryAttempts, pauseBetweenFailures, () => { | |
| product = httpClient.Get("https://example.com/api/products/1"); | |
| }); | |
| public static class RetryHelper | |
| { | |
| private static ILog logger = LogManager.GetLogger(); //use a logger or trace of your choice | |
| public static void RetryOnException(int times, TimeSpan delay, Action operation) | |
| { | |
| var attempts = 0; | |
| do | |
| { | |
| try | |
| { | |
| attempts++; | |
| operation(); | |
| break; // Sucess! Lets exit the loop! | |
| } | |
| catch (Exception ex) | |
| { | |
| if (attempts == times) | |
| throw; | |
| logger.Error($"Exception caught on attempt {attempts} - will retry after delay {delay}", ex); | |
| Task.Delay(delay).Wait(); | |
| } | |
| } while (true); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment