From 68ad1895c62f2ddb1525686e8655dac95ce7cf90 Mon Sep 17 00:00:00 2001 From: Cesar De la Torre Date: Wed, 15 Feb 2017 23:12:37 -0800 Subject: [PATCH] Fixed bug https://github.com/dotnet/eShopOnContainers/issues/45 with Retry with Exponential Backoff --- docker-compose.yml | 2 + src/Web/WebMVC/Services/CatalogService.cs | 15 +++- .../Utilities/RetryWithExponentialBackoff.cs | 90 +++++++++++++++++++ 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/Web/WebMVC/Services/Utilities/RetryWithExponentialBackoff.cs diff --git a/docker-compose.yml b/docker-compose.yml index 4e353f0b2..896663669 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -49,6 +49,8 @@ services: context: ./src/Web/WebMVC dockerfile: Dockerfile depends_on: + - catalog.api + - ordering.api - identity.api - basket.api diff --git a/src/Web/WebMVC/Services/CatalogService.cs b/src/Web/WebMVC/Services/CatalogService.cs index b830c9542..39576c43c 100644 --- a/src/Web/WebMVC/Services/CatalogService.cs +++ b/src/Web/WebMVC/Services/CatalogService.cs @@ -41,7 +41,20 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services } var catalogUrl = $"{_remoteServiceBaseUrl}items{filterQs}?pageIndex={page}&pageSize={take}"; - var dataString = await _apiClient.GetStringAsync(catalogUrl); + + var dataString = ""; + + // + // Using HttpClient with Retry and Exponential Backoff + // + var retry = new RetryWithExponentialBackoff(); + await retry.RunAsync(async () => + { + // work with HttpClient call + dataString = await _apiClient.GetStringAsync(catalogUrl); + }); + + //var dataString = await _apiClient.GetStringAsync(catalogUrl); var response = JsonConvert.DeserializeObject(dataString); return response; diff --git a/src/Web/WebMVC/Services/Utilities/RetryWithExponentialBackoff.cs b/src/Web/WebMVC/Services/Utilities/RetryWithExponentialBackoff.cs new file mode 100644 index 000000000..fe2404fe0 --- /dev/null +++ b/src/Web/WebMVC/Services/Utilities/RetryWithExponentialBackoff.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.eShopOnContainers.WebMVC.Services +{ + /// + /// When working with cloud services and Docker containers, it's very important to always catch + /// TimeoutException, and retry the operation. + /// RetryWithExponentialBackoff makes it easy to implement such pattern. + /// Usage: + /// var retry = new RetryWithExponentialBackoff(); + /// await retry.RunAsync(async ()=> + /// { + /// // work with HttpClient + /// }); + /// + public sealed class RetryWithExponentialBackoff + { + private readonly int maxRetries, delayMilliseconds, maxDelayMilliseconds; + + public RetryWithExponentialBackoff(int maxRetries = 5, int delayMilliseconds = 200, int maxDelayMilliseconds = 2000) + { + this.maxRetries = maxRetries; + this.delayMilliseconds = delayMilliseconds; + this.maxDelayMilliseconds = maxDelayMilliseconds; + } + + public async Task RunAsync(Func func) + { + ExponentialBackoff backoff = new ExponentialBackoff(this.maxRetries, this.delayMilliseconds, this.maxDelayMilliseconds); + retry: + try + { + await func(); + } + catch (Exception ex) when (ex is TimeoutException || ex is System.Net.Http.HttpRequestException) + { + Debug.WriteLine("Exception raised is: " + ex.GetType().ToString() + " -- Message: " + ex.Message + " -- Inner Message: " + ex.InnerException.Message); + await backoff.Delay(); + goto retry; + } + } + } + + + /// + /// Usage: + /// ExponentialBackoff backoff = new ExponentialBackoff(3, 10, 100); + /// retry: + /// try { + /// // ... + /// } + /// catch (Exception ex) { + /// await backoff.Delay(cancellationToken); + /// goto retry; + /// } + /// + public struct ExponentialBackoff + { + private readonly int m_maxRetries, m_delayMilliseconds, m_maxDelayMilliseconds; + private int m_retries, m_pow; + + public ExponentialBackoff(int maxRetries, int delayMilliseconds, int maxDelayMilliseconds) + { + m_maxRetries = maxRetries; + m_delayMilliseconds = delayMilliseconds; + m_maxDelayMilliseconds = maxDelayMilliseconds; + m_retries = 0; + m_pow = 1; + } + + public Task Delay() + { + if (m_retries == m_maxRetries) + { + throw new TimeoutException("Max retry attempts exceeded."); + } + ++m_retries; + if (m_retries < 31) + { + m_pow = m_pow << 1; // m_pow = Pow(2, m_retries - 1) + } + int delay = Math.Min(m_delayMilliseconds * (m_pow - 1) / 2, m_maxDelayMilliseconds); + return Task.Delay(delay); + } + } +}