Add circuit breakers.

Drop most cached times to 5 min.
Better scan error handling.
Better featured addons error handling.
This commit is contained in:
jliddev
2020-10-14 00:46:55 -05:00
parent 5c3cb2f7d5
commit a28bb3f0bf
8 changed files with 82 additions and 31 deletions

View File

@@ -5,6 +5,6 @@ namespace WowUp.Common.Services.Contracts
{
public interface ICacheService
{
Task<T> GetCache<T>(string cacheKey, Func<Task<T>> fallbackAction, int ttlMinutes = 60);
Task<T> GetCache<T>(string cacheKey, Func<Task<T>> fallbackAction, int ttlMinutes = 10);
}
}

View File

@@ -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<FlurlHttpException>()
.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<CurseSearchResult>();
});
.GetJsonAsync<CurseSearchResult>());
}, 5);
}
public async Task<IEnumerable<PotentialAddon>> 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<List<CurseSearchResult>>();
.ReceiveJson<List<CurseSearchResult>>());
}
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<IList<CurseSearchResult>>();
.GetJsonAsync<IList<CurseSearchResult>>());
}
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<CurseGetFeaturedResponse>();
});
.ReceiveJson<CurseGetFeaturedResponse>());
}, 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<CurseFingerprintsResponse>();
.ReceiveJson<CurseFingerprintsResponse>());
}
private PotentialAddon GetPotentialAddon(CurseSearchResult searchResult)

View File

@@ -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<FlurlHttpException>()
.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<List<TukUiAddon>>();
var result = await CircuitBreaker.ExecuteAsync(async () =>
await ApiUrl
.SetQueryParam(query, "all")
.WithTimeout(HttpTimeoutSeconds)
.WithHeaders(HttpUtilities.DefaultHeaders)
.GetJsonAsync<List<TukUiAddon>>());
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<TukUiAddon>();
}
private async Task<TukUiAddon> GetClientApiAddon(string addonName)
{
return await ClientApiUrl
return await CircuitBreaker.ExecuteAsync(async () => await ClientApiUrl
.SetQueryParam("ui", addonName)
.WithTimeout(HttpTimeoutSeconds)
.WithHeaders(HttpUtilities.DefaultHeaders)
.GetJsonAsync<TukUiAddon>();
.GetJsonAsync<TukUiAddon>());
}
private async Task<TukUiAddon> GetElvUiRetailAddon()

View File

@@ -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<FlurlHttpException>()
.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<List<AddonDetailsResponse>>();
.GetJsonAsync<List<AddonDetailsResponse>>());
return results.FirstOrDefault();
});
}, 5);
}
private string GetAddonId(Uri addonUri)

View File

@@ -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."

View File

@@ -159,11 +159,23 @@ namespace WowUp.WPF.Services
public async Task<List<PotentialAddon>> GetFeaturedAddons(WowClientType clientType)
{
var addonTasks = _providers.Select(p => p.GetFeaturedAddons(clientType));
var addonResults = await Task.WhenAll(addonTasks);
var addonResultsConcat = addonResults.SelectMany(res => res);
List<PotentialAddon> addonResults = new List<PotentialAddon>();
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<List<Addon>> GetAddons(WowClientType clientType, bool rescan = false)

View File

@@ -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;

View File

@@ -10,7 +10,7 @@
<PackageId>WowUp</PackageId>
<Authors>Jliddev</Authors>
<Product>WowUp</Product>
<Version>1.18.2</Version>
<Version>1.18.3</Version>
<ApplicationIcon>wowup_logo_512np_RRT_icon.ico</ApplicationIcon>
<Copyright>jliddev</Copyright>
<PackageProjectUrl>https://wowup.io</PackageProjectUrl>
@@ -62,6 +62,7 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.7" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.19" />
<PackageReference Include="Polly" Version="7.2.1" />
<PackageReference Include="protobuf-net" Version="3.0.29" />
<PackageReference Include="Serilog" Version="2.9.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />