diff --git a/ConsoleHelper/Program.cs b/ConsoleHelper/Program.cs index 92f37141..8fcfe44b 100644 --- a/ConsoleHelper/Program.cs +++ b/ConsoleHelper/Program.cs @@ -38,7 +38,7 @@ static void Main(string[] args) new ScalableCapitalParser(api), new Trading212Parser(api), }); - //t.DoWork().Wait(); + t.DoWork().Wait(); t = new MarketDataMaintainerTask(logger, api, cs); t.DoWork().Wait(); } diff --git a/GhostfolioSidekick/Ghostfolio/API/GhostfolioAPI.cs b/GhostfolioSidekick/Ghostfolio/API/GhostfolioAPI.cs index 4e90dfa8..2e236483 100644 --- a/GhostfolioSidekick/Ghostfolio/API/GhostfolioAPI.cs +++ b/GhostfolioSidekick/Ghostfolio/API/GhostfolioAPI.cs @@ -148,11 +148,22 @@ public async Task UpdateAccount(Model.Account account) var sourceCurrency = mapper.MapCurrency(money.Currency.Symbol); - var content = await restCall.DoRestGet($"api/v1/exchange-rate/{sourceCurrency}-{targetCurrency.Symbol}/{date:yyyy-MM-dd}", CacheDuration.Short()); + decimal rate = 1; + try + { + var content = await restCall.DoRestGet($"api/v1/exchange-rate/{sourceCurrency}-{targetCurrency.Symbol}/{date:yyyy-MM-dd}", CacheDuration.Short(), true); - dynamic stuff = JsonConvert.DeserializeObject(content); - var token = stuff.marketPrice.ToString(); - var rate = (decimal)decimal.Parse(token); + if (content != null) + { + dynamic stuff = JsonConvert.DeserializeObject(content); + var token = stuff.marketPrice.ToString(); + rate = (decimal)decimal.Parse(token); + } + } + catch + { + logger.LogWarning($"Exchange rate not found for {sourceCurrency}-{targetCurrency.Symbol} on {date}. Assuming rate of 1"); + } if (rate == 1) { diff --git a/GhostfolioSidekick/Ghostfolio/API/RestCall.cs b/GhostfolioSidekick/Ghostfolio/API/RestCall.cs index 7ecaef1b..b19b89cb 100644 --- a/GhostfolioSidekick/Ghostfolio/API/RestCall.cs +++ b/GhostfolioSidekick/Ghostfolio/API/RestCall.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Polly; +using Polly.CircuitBreaker; using Polly.Retry; using RestSharp; using System.Diagnostics; @@ -21,6 +22,7 @@ public class RestCall private readonly string url; private readonly string accessToken; private readonly RetryPolicy retryPolicy; + private readonly CircuitBreakerPolicy basicCircuitBreakerPolicy; public RestCall( IMemoryCache memoryCache, @@ -39,15 +41,31 @@ public RestCall( { logger.LogDebug($"The request failed. HttpStatusCode={iRestResponse.Result.StatusCode}. Waiting {timeSpan} seconds before retry. Number attempt {retryCount}. Uri={iRestResponse.Result.ResponseUri};"); }); + + basicCircuitBreakerPolicy = Policy + .HandleResult(r => !r.IsSuccessStatusCode) + .CircuitBreaker(2, TimeSpan.FromSeconds(30), (iRestResponse, timeSpan) => + { + logger.LogDebug($"Circuit Breaker on a break"); + }, () => + { + logger.LogDebug($"Circuit Breaker active"); + }); } - public async Task DoRestGet(string suffixUrl, MemoryCacheEntryOptions cacheEntryOptions) + public async Task DoRestGet(string suffixUrl, MemoryCacheEntryOptions cacheEntryOptions, bool useCircuitBreaker = false) { if (memoryCache.TryGetValue(suffixUrl, out var result)) { return result; } + Policy policy = retryPolicy; + if (useCircuitBreaker) + { + policy = basicCircuitBreakerPolicy.Wrap(retryPolicy); + } + try { mutex.WaitOne(); @@ -75,7 +93,8 @@ public RestCall( var stopwatch = new Stopwatch(); stopwatch.Start(); - var r = retryPolicy.Execute(() => client.ExecuteGetAsync(request).Result); + + var r = policy.Execute(() => client.ExecuteGetAsync(request).Result); stopwatch.Stop(); logger.LogDebug($"Url {url}/{suffixUrl} took {stopwatch.ElapsedMilliseconds}ms"); @@ -92,6 +111,10 @@ public RestCall( return r.Content; } + catch (BrokenCircuitException) + { + return null; + } finally { //Call the ReleaseMutex method to unblock so that other threads