Upgrade to Polly 8 usage

This commit is contained in:
Tyrrrz 2023-09-28 19:30:12 +03:00
parent fbfff4e51f
commit a58509fda8
3 changed files with 78 additions and 58 deletions

View file

@ -33,7 +33,7 @@ public class DiscordClient
CancellationToken cancellationToken = default CancellationToken cancellationToken = default
) )
{ {
return await Http.ResponseResiliencePolicy.ExecuteAsync( return await Http.ResponseResiliencePipeline.ExecuteAsync(
async innerCancellationToken => async innerCancellationToken =>
{ {
using var request = new HttpRequestMessage(HttpMethod.Get, new Uri(_baseUri, url)); using var request = new HttpRequestMessage(HttpMethod.Get, new Uri(_baseUri, url));

View file

@ -53,44 +53,47 @@ internal partial class ExportAssetDownloader
Directory.CreateDirectory(_workingDirPath); Directory.CreateDirectory(_workingDirPath);
await Http.ResiliencePolicy.ExecuteAsync(async () => await Http.ResiliencePipeline.ExecuteAsync(
{ async innerCancellationToken =>
// Download the file
using var response = await Http.Client.GetAsync(url, cancellationToken);
await using (var output = File.Create(filePath))
await response.Content.CopyToAsync(output, cancellationToken);
// Try to set the file date according to the last-modified header
try
{ {
var lastModified = response.Content.Headers // Download the file
.TryGetValue("Last-Modified") using var response = await Http.Client.GetAsync(url, innerCancellationToken);
?.Pipe( await using (var output = File.Create(filePath))
s => await response.Content.CopyToAsync(output, innerCancellationToken);
DateTimeOffset.TryParse(
s,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out var instant
)
? instant
: (DateTimeOffset?)null
);
if (lastModified is not null) // Try to set the file date according to the last-modified header
try
{ {
File.SetCreationTimeUtc(filePath, lastModified.Value.UtcDateTime); var lastModified = response.Content.Headers
File.SetLastWriteTimeUtc(filePath, lastModified.Value.UtcDateTime); .TryGetValue("Last-Modified")
File.SetLastAccessTimeUtc(filePath, lastModified.Value.UtcDateTime); ?.Pipe(
s =>
DateTimeOffset.TryParse(
s,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out var instant
)
? instant
: (DateTimeOffset?)null
);
if (lastModified is not null)
{
File.SetCreationTimeUtc(filePath, lastModified.Value.UtcDateTime);
File.SetLastWriteTimeUtc(filePath, lastModified.Value.UtcDateTime);
File.SetLastAccessTimeUtc(filePath, lastModified.Value.UtcDateTime);
}
} }
} catch
catch {
{ // This can apparently fail for some reason.
// This can apparently fail for some reason. // Updating the file date is not a critical task, so we'll just ignore exceptions thrown here.
// Updating the file date is not a critical task, so we'll just ignore exceptions thrown here. // https://github.com/Tyrrrz/DiscordChatExporter/issues/585
// https://github.com/Tyrrrz/DiscordChatExporter/issues/585 }
} },
}); cancellationToken
);
return _previousPathsByUrl[url] = filePath; return _previousPathsByUrl[url] = filePath;
} }

View file

@ -7,6 +7,7 @@ using System.Security.Authentication;
using System.Threading.Tasks; using System.Threading.Tasks;
using DiscordChatExporter.Core.Utils.Extensions; using DiscordChatExporter.Core.Utils.Extensions;
using Polly; using Polly;
using Polly.Retry;
namespace DiscordChatExporter.Core.Utils; namespace DiscordChatExporter.Core.Utils;
@ -31,29 +32,45 @@ public static class Http
&& IsRetryableStatusCode(hrex.StatusCode ?? HttpStatusCode.OK) && IsRetryableStatusCode(hrex.StatusCode ?? HttpStatusCode.OK)
); );
public static IAsyncPolicy ResiliencePolicy { get; } = public static ResiliencePipeline ResiliencePipeline { get; } =
Policy new ResiliencePipelineBuilder()
.Handle<Exception>(IsRetryableException) .AddRetry(
.WaitAndRetryAsync(4, i => TimeSpan.FromSeconds(Math.Pow(2, i) + 1)); new RetryStrategyOptions
public static IAsyncPolicy<HttpResponseMessage> ResponseResiliencePolicy { get; } =
Policy
.Handle<Exception>(IsRetryableException)
.OrResult<HttpResponseMessage>(m => IsRetryableStatusCode(m.StatusCode))
.WaitAndRetryAsync(
8,
(i, result, _) =>
{ {
// If rate-limited, use retry-after header as the guide. ShouldHandle = new PredicateBuilder().Handle<Exception>(IsRetryableException),
// The response can be null here if an exception was thrown. MaxRetryAttempts = 4,
if (result.Result?.Headers.RetryAfter?.Delta is { } retryAfter) BackoffType = DelayBackoffType.Exponential,
{ Delay = TimeSpan.FromSeconds(1)
// Add some buffer just in case }
return retryAfter + TimeSpan.FromSeconds(1); )
} .Build();
return TimeSpan.FromSeconds(Math.Pow(2, i) + 1); public static ResiliencePipeline<HttpResponseMessage> ResponseResiliencePipeline { get; } =
}, new ResiliencePipelineBuilder<HttpResponseMessage>()
(_, _, _, _) => Task.CompletedTask .AddRetry(
); new RetryStrategyOptions<HttpResponseMessage>
{
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.Handle<Exception>(IsRetryableException)
.HandleResult(m => IsRetryableStatusCode(m.StatusCode)),
MaxRetryAttempts = 8,
DelayGenerator = args =>
{
// If rate-limited, use retry-after header as the guide.
// The response can be null here if an exception was thrown.
if (args.Outcome.Result?.Headers.RetryAfter?.Delta is { } retryAfter)
{
// Add some buffer just in case
return ValueTask.FromResult<TimeSpan?>(
retryAfter + TimeSpan.FromSeconds(1)
);
}
return ValueTask.FromResult<TimeSpan?>(
TimeSpan.FromSeconds(Math.Pow(2, args.AttemptNumber) + 1)
);
}
}
)
.Build();
} }