From a28bb3f0bfd7f934d67bf73fdb0350ffc0d210c2 Mon Sep 17 00:00:00 2001 From: jliddev Date: Wed, 14 Oct 2020 00:46:55 -0500 Subject: [PATCH] Add circuit breakers. Drop most cached times to 5 min. Better scan error handling. Better featured addons error handling. --- .../Services/Contracts/ICacheService.cs | 2 +- .../AddonProviders/CurseAddonProvider.cs | 33 ++++++++++++------- .../AddonProviders/TukUiAddonProvider.cs | 29 +++++++++++----- .../WowInterfaceAddonProvider.cs | 15 +++++++-- WowUp.WPF/Assets/changelog.json | 4 +++ WowUp.WPF/Services/AddonService.cs | 20 ++++++++--- WowUp.WPF/ViewModels/GetAddonsViewModel.cs | 7 +++- WowUp.WPF/WowUp.WPF.csproj | 3 +- 8 files changed, 82 insertions(+), 31 deletions(-) diff --git a/WowUp.Common/Services/Contracts/ICacheService.cs b/WowUp.Common/Services/Contracts/ICacheService.cs index a9e269b2..ec412d71 100644 --- a/WowUp.Common/Services/Contracts/ICacheService.cs +++ b/WowUp.Common/Services/Contracts/ICacheService.cs @@ -5,6 +5,6 @@ namespace WowUp.Common.Services.Contracts { public interface ICacheService { - Task GetCache(string cacheKey, Func> fallbackAction, int ttlMinutes = 60); + Task GetCache(string cacheKey, Func> fallbackAction, int ttlMinutes = 10); } } diff --git a/WowUp.WPF/AddonProviders/CurseAddonProvider.cs b/WowUp.WPF/AddonProviders/CurseAddonProvider.cs index 7f07b751..424bf05d 100644 --- a/WowUp.WPF/AddonProviders/CurseAddonProvider.cs +++ b/WowUp.WPF/AddonProviders/CurseAddonProvider.cs @@ -1,5 +1,6 @@ using Flurl; using Flurl.Http; +using Polly; using Serilog; using System; using System.Collections.Concurrent; @@ -32,6 +33,14 @@ namespace WowUp.WPF.AddonProviders private readonly ICacheService _cacheService; private readonly IAnalyticsService _analyticsService; + private readonly AsyncPolicy CircuitBreaker = Policy + .Handle() + .CircuitBreakerAsync( + 2, + TimeSpan.FromMinutes(1), + (ex, ts) => { Log.Error(ex, "Curse CircuitBreaker broken"); }, + () => { Log.Information("Curse CircuitBreaker reset"); }); + public string Name => "Curse"; public CurseAddonProvider( @@ -119,11 +128,11 @@ namespace WowUp.WPF.AddonProviders return await _cacheService.GetCache(url, async () => { - return await url + return await CircuitBreaker.ExecuteAsync(async () => await url .WithHeaders(HttpUtilities.DefaultHeaders) .WithTimeout(HttpTimeoutSeconds) - .GetJsonAsync(); - }); + .GetJsonAsync()); + }, 5); } public async Task> Search(string query, WowClientType clientType) @@ -492,11 +501,11 @@ namespace WowUp.WPF.AddonProviders try { - return await url + return await CircuitBreaker.ExecuteAsync(async () => await url .WithHeaders(HttpUtilities.DefaultHeaders) .WithTimeout(HttpTimeoutSeconds) .PostJsonAsync(addonIds.Select(id => Convert.ToInt32(id)).ToArray()) - .ReceiveJson>(); + .ReceiveJson>()); } catch (Exception ex) { @@ -512,11 +521,11 @@ namespace WowUp.WPF.AddonProviders try { - return await url + return await CircuitBreaker.ExecuteAsync(async () => await url .SetQueryParams(new { gameId = 1, searchFilter = query }) .WithTimeout(HttpTimeoutSeconds) .WithHeaders(HttpUtilities.DefaultHeaders) - .GetJsonAsync>(); + .GetJsonAsync>()); } catch (Exception ex) { @@ -542,12 +551,12 @@ namespace WowUp.WPF.AddonProviders var response = await _cacheService.GetCache(url, async () => { - return await url + return await CircuitBreaker.ExecuteAsync(async () => await url .WithHeaders(HttpUtilities.DefaultHeaders) .WithTimeout(HttpTimeoutSeconds) .PostJsonAsync(body) - .ReceiveJson(); - }); + .ReceiveJson()); + }, 5); return response.Popular.ToList(); } @@ -562,11 +571,11 @@ namespace WowUp.WPF.AddonProviders { var url = $"{ApiUrl}/fingerprint"; - return await url + return await CircuitBreaker.ExecuteAsync(async () => await url .WithHeaders(HttpUtilities.DefaultHeaders) .WithTimeout(HttpTimeoutSeconds) .PostJsonAsync(fingerprints) - .ReceiveJson(); + .ReceiveJson()); } private PotentialAddon GetPotentialAddon(CurseSearchResult searchResult) diff --git a/WowUp.WPF/AddonProviders/TukUiAddonProvider.cs b/WowUp.WPF/AddonProviders/TukUiAddonProvider.cs index ce511adb..7bd2342a 100644 --- a/WowUp.WPF/AddonProviders/TukUiAddonProvider.cs +++ b/WowUp.WPF/AddonProviders/TukUiAddonProvider.cs @@ -1,5 +1,7 @@ using Flurl; using Flurl.Http; +using Polly; +using Polly.CircuitBreaker; using Serilog; using System; using System.Collections.Generic; @@ -28,6 +30,14 @@ namespace WowUp.WPF.AddonProviders private readonly ICacheService _cacheService; private readonly IAnalyticsService _analyticsService; + private readonly AsyncPolicy CircuitBreaker = Policy + .Handle() + .CircuitBreakerAsync( + 2, + TimeSpan.FromMinutes(1), + (ex, ts) => { Log.Error(ex, "TukUI CircuitBreaker broken"); }, + () => { Log.Information("TukUI CircuitBreaker reset"); }); + public string Name => "TukUI"; public TukUiAddonProvider( @@ -229,11 +239,12 @@ namespace WowUp.WPF.AddonProviders { var query = GetAddonsSuffix(clientType); - var result = await ApiUrl - .SetQueryParam(query, "all") - .WithTimeout(HttpTimeoutSeconds) - .WithHeaders(HttpUtilities.DefaultHeaders) - .GetJsonAsync>(); + var result = await CircuitBreaker.ExecuteAsync(async () => + await ApiUrl + .SetQueryParam(query, "all") + .WithTimeout(HttpTimeoutSeconds) + .WithHeaders(HttpUtilities.DefaultHeaders) + .GetJsonAsync>()); if (clientType.IsRetail()) { @@ -246,20 +257,20 @@ namespace WowUp.WPF.AddonProviders catch (Exception ex) { Log.Error(ex, "Failed to get all addons"); - return null; + throw; } - }); + }, 5); return results ?? new List(); } private async Task GetClientApiAddon(string addonName) { - return await ClientApiUrl + return await CircuitBreaker.ExecuteAsync(async () => await ClientApiUrl .SetQueryParam("ui", addonName) .WithTimeout(HttpTimeoutSeconds) .WithHeaders(HttpUtilities.DefaultHeaders) - .GetJsonAsync(); + .GetJsonAsync()); } private async Task GetElvUiRetailAddon() diff --git a/WowUp.WPF/AddonProviders/WowInterfaceAddonProvider.cs b/WowUp.WPF/AddonProviders/WowInterfaceAddonProvider.cs index 21ab53e6..b96d429f 100644 --- a/WowUp.WPF/AddonProviders/WowInterfaceAddonProvider.cs +++ b/WowUp.WPF/AddonProviders/WowInterfaceAddonProvider.cs @@ -1,4 +1,5 @@ using Flurl.Http; +using Polly; using Serilog; using System; using System.Collections.Generic; @@ -27,6 +28,14 @@ namespace WowUp.WPF.AddonProviders private readonly IAnalyticsService _analyticsService; private readonly ICacheService _cacheService; + private readonly AsyncPolicy CircuitBreaker = Policy + .Handle() + .CircuitBreakerAsync( + 2, + TimeSpan.FromMinutes(1), + (ex, ts) => { Log.Error(ex, "WowInterface CircuitBreaker broken"); }, + () => { Log.Information("WowInterface CircuitBreaker reset"); }); + public string Name => "WowInterface"; public WowInterfaceAddonProvider( @@ -153,13 +162,13 @@ namespace WowUp.WPF.AddonProviders return await _cacheService.GetCache(url, async () => { - var results = await url + var results = await CircuitBreaker.ExecuteAsync(async () => await url .WithHeaders(HttpUtilities.DefaultHeaders) .WithTimeout(HttpTimeoutSeconds) - .GetJsonAsync>(); + .GetJsonAsync>()); return results.FirstOrDefault(); - }); + }, 5); } private string GetAddonId(Uri addonUri) diff --git a/WowUp.WPF/Assets/changelog.json b/WowUp.WPF/Assets/changelog.json index 491d3d52..075fde09 100644 --- a/WowUp.WPF/Assets/changelog.json +++ b/WowUp.WPF/Assets/changelog.json @@ -1,5 +1,9 @@ { "ChangeLogs": [ + { + "Version": "1.18.3", + "Description": "Add cuircuit breakers to reduce chaining failing calls onto apis." + }, { "Version": "1.18.2", "Description": "Add some shorter HTTP timeout times (4 seconds).\nAdd some feedback when scanning from a provider fails.\nFix a bug with CF sending empty current files." diff --git a/WowUp.WPF/Services/AddonService.cs b/WowUp.WPF/Services/AddonService.cs index abd24bb1..9cf426d2 100644 --- a/WowUp.WPF/Services/AddonService.cs +++ b/WowUp.WPF/Services/AddonService.cs @@ -159,11 +159,23 @@ namespace WowUp.WPF.Services public async Task> GetFeaturedAddons(WowClientType clientType) { - var addonTasks = _providers.Select(p => p.GetFeaturedAddons(clientType)); - var addonResults = await Task.WhenAll(addonTasks); - var addonResultsConcat = addonResults.SelectMany(res => res); + List addonResults = new List(); + foreach (var provider in _providers) + { + try + { + var result = await provider.GetFeaturedAddons(clientType); + addonResults.AddRange(result); + } + catch(Exception ex) + { + Log.Error(ex, $"Failed to get feature addons from {provider.Name}"); + } + } - return addonResultsConcat.OrderByDescending(result => result.DownloadCount).ToList(); + return addonResults + .OrderByDescending(result => result.DownloadCount) + .ToList(); } public async Task> GetAddons(WowClientType clientType, bool rescan = false) diff --git a/WowUp.WPF/ViewModels/GetAddonsViewModel.cs b/WowUp.WPF/ViewModels/GetAddonsViewModel.cs index c2947a0b..38699303 100644 --- a/WowUp.WPF/ViewModels/GetAddonsViewModel.cs +++ b/WowUp.WPF/ViewModels/GetAddonsViewModel.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Serilog; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -213,7 +214,7 @@ namespace WowUp.WPF.ViewModels try { - if(SelectedClientType == WowClientType.None) + if (SelectedClientType == WowClientType.None) { return; } @@ -240,6 +241,10 @@ namespace WowUp.WPF.ViewModels SetResultCountContextText(DisplayAddons.Count); } + catch (Exception ex) + { + Log.Error(ex, "failed to get popular addons"); + } finally { IsBusy = false; diff --git a/WowUp.WPF/WowUp.WPF.csproj b/WowUp.WPF/WowUp.WPF.csproj index 2bf62b87..28020cc4 100644 --- a/WowUp.WPF/WowUp.WPF.csproj +++ b/WowUp.WPF/WowUp.WPF.csproj @@ -10,7 +10,7 @@ WowUp Jliddev WowUp - 1.18.2 + 1.18.3 wowup_logo_512np_RRT_icon.ico jliddev https://wowup.io @@ -62,6 +62,7 @@ +