Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Design improvement #58

Merged
merged 6 commits into from
Nov 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Dodo.HttpClientResiliencePolicies.RetryPolicy;
using Microsoft.Extensions.DependencyInjection;
using Polly;
using Polly.CircuitBreaker;
using Polly.Extensions.Http;
using Polly.Registry;
using Polly.Timeout;
Expand Down Expand Up @@ -54,10 +53,7 @@ private static IHttpClientBuilder AddRetryPolicy(
.AddPolicyHandler(HttpPolicyExtensions
.HandleTransientHttpError()
.Or<TimeoutRejectedException>()
.WaitAndRetryAsync(
settings.RetryCount,
settings.SleepDurationProviderWrapper,
settings.OnRetryWrapper));
.WaitAndRetryAsync(settings));
}

private static IHttpClientBuilder AddCircuitBreakerPolicy(
Expand All @@ -78,21 +74,14 @@ private static IHttpClientBuilder AddCircuitBreakerPolicy(
});
}

private static AsyncCircuitBreakerPolicy<HttpResponseMessage> BuildCircuitBreakerPolicy(
private static IAsyncPolicy<HttpResponseMessage> BuildCircuitBreakerPolicy(
CircuitBreakerPolicySettings settings)
{
return HttpPolicyExtensions
.HandleTransientHttpError()
.Or<TimeoutRejectedException>()
.OrResult(r => r.StatusCode == (HttpStatusCode) 429) // Too Many Requests
.AdvancedCircuitBreakerAsync(
settings.FailureThreshold,
settings.SamplingDuration,
settings.MinimumThroughput,
settings.DurationOfBreak,
settings.OnBreak,
settings.OnReset,
settings.OnHalfOpen);
.AdvancedCircuitBreakerAsync(settings);
}

private static IHttpClientBuilder AddTimeoutPolicy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public sealed class ResiliencePoliciesSettings
private CircuitBreakerPolicySettings _circuitBreakerPolicySettings = new CircuitBreakerPolicySettings();

public TimeSpan OverallTimeout { get; set; } = TimeSpan.FromMilliseconds(Defaults.Timeout.TimeoutOverallInMilliseconds);
public TimeSpan TimeoutPerTry { get; set; }= TimeSpan.FromMilliseconds(Defaults.Timeout.TimeoutPerTryInMilliseconds);
public TimeSpan TimeoutPerTry { get; set; } = TimeSpan.FromMilliseconds(Defaults.Timeout.TimeoutPerTryInMilliseconds);

public RetryPolicySettings RetryPolicySettings
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;

namespace Dodo.HttpClientResiliencePolicies.RetryPolicy
{
public interface ISleepDurationProvider
{
int RetryCount { get; }
IEnumerable<TimeSpan> Durations { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Net.Http;
using Dodo.HttpClientResiliencePolicies.CircuitBreakerPolicy;
using Polly;

namespace Dodo.HttpClientResiliencePolicies.RetryPolicy
{
internal static class PolicyBuilderExtension
{
public static IAsyncPolicy<HttpResponseMessage> WaitAndRetryAsync(
this PolicyBuilder<HttpResponseMessage> policyBuilder,
RetryPolicySettings settings)
{
var handler = new RetryPolicyHandler(settings);
return policyBuilder
.WaitAndRetryAsync(
handler.RetryCount,
handler.SleepDurationProvider,
handler.OnRetry);
}

public static IAsyncPolicy<HttpResponseMessage> AdvancedCircuitBreakerAsync(
this PolicyBuilder<HttpResponseMessage> policyBuilder,
CircuitBreakerPolicySettings settings)
{
return policyBuilder
.AdvancedCircuitBreakerAsync(
settings.FailureThreshold,
settings.SamplingDuration,
settings.MinimumThroughput,
settings.DurationOfBreak,
settings.OnBreak,
settings.OnReset,
settings.OnHalfOpen);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Polly;

namespace Dodo.HttpClientResiliencePolicies.RetryPolicy
{
internal sealed class RetryPolicyHandler
{
private readonly RetryPolicySettings _retryPolicySettings;

internal RetryPolicyHandler(RetryPolicySettings retryPolicySettings)
{
_retryPolicySettings = retryPolicySettings;
}

public int RetryCount => _retryPolicySettings.SleepProvider.RetryCount;

public TimeSpan SleepDurationProvider(int retryCount, DelegateResult<HttpResponseMessage> response, Context context)
{
var serverWaitDuration = GetServerWaitDuration(response);
// ReSharper disable once PossibleMultipleEnumeration
return serverWaitDuration ?? _retryPolicySettings.SleepProvider.Durations.ToArray()[retryCount-1];
}

public Task OnRetry(DelegateResult<HttpResponseMessage> response, TimeSpan span, int retryCount, Context context)
{
_retryPolicySettings.OnRetry?.Invoke(response, span);
//todo bulanova: не нравится что асихронный метод в синхронный превращается
return Task.CompletedTask;
}

private static TimeSpan? GetServerWaitDuration(DelegateResult<HttpResponseMessage> response)
{
var retryAfter = response?.Result?.Headers?.RetryAfter;
if (retryAfter == null)
{
return null;
}

if (retryAfter.Delta.HasValue) // Delta priority check, because its simple TimeSpan value
{
return retryAfter.Delta.Value;
}

if (retryAfter.Date.HasValue)
{
return retryAfter.Date.Value - DateTime.UtcNow;
}

return null; // when nothing was found
}
}
}
Original file line number Diff line number Diff line change
@@ -1,110 +1,85 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Polly;

namespace Dodo.HttpClientResiliencePolicies.RetryPolicy
{
public sealed partial class RetryPolicySettings
public sealed class RetryPolicySettings
{
public int RetryCount { get; }

private readonly Func<int, DelegateResult<HttpResponseMessage>, Context, TimeSpan> _sleepDurationProvider;
internal Func<int, DelegateResult<HttpResponseMessage>, Context, TimeSpan> SleepDurationProviderWrapper =>
(retryCount, response, context) =>
{
var serverWaitDuration = GetServerWaitDuration(response);
return serverWaitDuration ?? _sleepDurationProvider(retryCount, response, context);
};
internal ISleepDurationProvider SleepProvider { get; }

internal Action<DelegateResult<HttpResponseMessage>, TimeSpan> OnRetry { get; set; }
internal Func<DelegateResult<HttpResponseMessage>, TimeSpan, int, Context, Task> OnRetryWrapper =>
(response, span, retryCount, context) =>
{
OnRetry?.Invoke(response, span);
return Task.CompletedTask;
};

public RetryPolicySettings()
public RetryPolicySettings(
ISleepDurationProvider provider)
{
_sleepDurationProvider = SleepDurationProvider.Jitter(
Defaults.Retry.RetryCount,
TimeSpan.FromMilliseconds(Defaults.Retry.MedianFirstRetryDelayInMilliseconds));
if (provider == null) throw new ArgumentNullException(nameof(provider));

SleepProvider = provider;
OnRetry = DoNothingOnRetry;
RetryCount = Defaults.Retry.RetryCount;
}

private RetryPolicySettings(
int retryCount,
Func<int, DelegateResult<HttpResponseMessage>, Context, TimeSpan> sleepDurationProvider)
public RetryPolicySettings()
: this(SleepDurationProvider.Jitter(
Defaults.Retry.RetryCount,
TimeSpan.FromMilliseconds(Defaults.Retry.MedianFirstRetryDelayInMilliseconds)))
{
_sleepDurationProvider = sleepDurationProvider;
OnRetry = DoNothingOnRetry;
RetryCount = retryCount;
}

private static readonly Action<DelegateResult<HttpResponseMessage>, TimeSpan> DoNothingOnRetry = (_, __) => { };

public static RetryPolicySettings Constant(int retryCount)
{
return Constant(retryCount, TimeSpan.FromMilliseconds(Defaults.Retry.InitialDelayMilliseconds));
return Constant(retryCount,
TimeSpan.FromMilliseconds(Defaults.Retry.InitialDelayMilliseconds));
}

public static RetryPolicySettings Constant(int retryCount, TimeSpan delay)
public static RetryPolicySettings Constant(int retryCount, TimeSpan initialDelay)
{
return new RetryPolicySettings(retryCount, SleepDurationProvider.Constant(retryCount, delay));
return new RetryPolicySettings(
SleepDurationProvider.Constant(retryCount,initialDelay));
}

public static RetryPolicySettings Linear(int retryCount)
{
return Linear(retryCount, TimeSpan.FromMilliseconds(Defaults.Retry.InitialDelayMilliseconds));
return Linear(retryCount,
TimeSpan.FromMilliseconds(Defaults.Retry.InitialDelayMilliseconds));
}

public static RetryPolicySettings Linear(int retryCount, TimeSpan initialDelay)
{
return new RetryPolicySettings(retryCount, SleepDurationProvider.Constant(retryCount, initialDelay));
return new RetryPolicySettings(
SleepDurationProvider.Linear(retryCount, initialDelay));
}

public static RetryPolicySettings Exponential(int retryCount)
{
return Exponential(retryCount, TimeSpan.FromMilliseconds(Defaults.Retry.InitialDelayMilliseconds));
return Exponential(retryCount,
TimeSpan.FromMilliseconds(Defaults.Retry.InitialDelayMilliseconds));
}

public static RetryPolicySettings Exponential(int retryCount, TimeSpan initialDelay)
{
return new RetryPolicySettings(retryCount, SleepDurationProvider.Exponential(retryCount, initialDelay));
return new RetryPolicySettings(
SleepDurationProvider.Exponential(retryCount, initialDelay));
}

public static RetryPolicySettings Jitter(int retryCount)
public static RetryPolicySettings Jitter()
{
return Jitter(retryCount, TimeSpan.FromMilliseconds(Defaults.Retry.MedianFirstRetryDelayInMilliseconds));
return Jitter(Defaults.Retry.RetryCount,
TimeSpan.FromMilliseconds(Defaults.Retry.MedianFirstRetryDelayInMilliseconds));
}

public static RetryPolicySettings Jitter(int retryCount, TimeSpan medianFirstRetryDelay)
public static RetryPolicySettings Jitter(int retryCount)
{
return new RetryPolicySettings(retryCount, SleepDurationProvider.Jitter(retryCount, medianFirstRetryDelay));
return Jitter(retryCount,
TimeSpan.FromMilliseconds(Defaults.Retry.MedianFirstRetryDelayInMilliseconds));
}

private static TimeSpan? GetServerWaitDuration(DelegateResult<HttpResponseMessage> response)
public static RetryPolicySettings Jitter(int retryCount, TimeSpan medianFirstRetryDelay)
{
var retryAfter = response?.Result?.Headers?.RetryAfter;
if (retryAfter == null)
{
return null;
}

if (retryAfter.Delta.HasValue) // Delta priority check, because its simple TimeSpan value
{
return retryAfter.Delta.Value;
}

if (retryAfter.Date.HasValue)
{
return retryAfter.Date.Value - DateTime.UtcNow;
}

return null; // when nothing was found
return new RetryPolicySettings(
SleepDurationProvider.Jitter(retryCount, medianFirstRetryDelay));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,46 +1,57 @@
using System;
using System.Linq;
using System.Net.Http;
using Polly;
using System.Collections.Generic;
using Polly.Contrib.WaitAndRetry;

namespace Dodo.HttpClientResiliencePolicies.RetryPolicy
{
public sealed partial class RetryPolicySettings
public sealed class SleepDurationProvider : ISleepDurationProvider
{
private static class SleepDurationProvider
public int RetryCount { get; }
public IEnumerable<TimeSpan> Durations { get; }

public SleepDurationProvider(int retryCount, IEnumerable<TimeSpan> durations)
{
internal static Func<int, DelegateResult<HttpResponseMessage>, Context, TimeSpan> Constant(int retryCount, TimeSpan delay)
{
if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), retryCount, "should be >= 0");
if (delay < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(delay), delay, "should be >= 0ms");
if (durations == null) throw new ArgumentNullException(nameof(durations));
if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), retryCount, "should be >= 0");

return (i, r, c) => Backoff.ConstantBackoff(delay, retryCount).ToArray()[i - 1];
}
RetryCount = retryCount;
Durations = durations;
}

internal static Func<int, DelegateResult<HttpResponseMessage>, Context, TimeSpan> Linear(int retryCount, TimeSpan initialDelay)
{
if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), retryCount, "should be >= 0");
if (initialDelay < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(initialDelay), initialDelay, "should be >= 0ms");
public static SleepDurationProvider Constant(int retryCount, TimeSpan initialDelay)
{
if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), retryCount, "should be >= 0");
if (initialDelay < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(initialDelay), initialDelay, "should be >= 0ms");

return (i, r, c) => Backoff.LinearBackoff(initialDelay, retryCount).ToArray()[i - 1];
}
return new SleepDurationProvider(retryCount, Backoff.ConstantBackoff(initialDelay, retryCount));
}

internal static Func<int, DelegateResult<HttpResponseMessage>, Context, TimeSpan> Exponential(int retryCount, TimeSpan initialDelay)
{
if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), retryCount, "should be >= 0");
if (initialDelay < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(initialDelay), initialDelay, "should be >= 0ms");
public static SleepDurationProvider Linear(int retryCount, TimeSpan initialDelay)
{
if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), retryCount, "should be >= 0");
if (initialDelay < TimeSpan.Zero)
throw new ArgumentOutOfRangeException(nameof(initialDelay), initialDelay, "should be >= 0ms");

return (i, r, c) => Backoff.ExponentialBackoff(initialDelay, retryCount).ToArray()[i - 1];
}
return new SleepDurationProvider(retryCount, Backoff.LinearBackoff(initialDelay, retryCount));
}

internal static Func<int, DelegateResult<HttpResponseMessage>, Context, TimeSpan> Jitter(int retryCount, TimeSpan medianFirstRetryDelay)
{
if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), retryCount, "should be >= 0");
if (medianFirstRetryDelay < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(medianFirstRetryDelay), medianFirstRetryDelay, "should be >= 0ms");
public static SleepDurationProvider Exponential(int retryCount, TimeSpan initialDelay)
{
if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), retryCount, "should be >= 0");
if (initialDelay < TimeSpan.Zero)
throw new ArgumentOutOfRangeException(nameof(initialDelay), initialDelay, "should be >= 0ms");

return new SleepDurationProvider(retryCount, Backoff.ExponentialBackoff(initialDelay, retryCount));
}

public static SleepDurationProvider Jitter(int retryCount, TimeSpan medianFirstRetryDelay)
{
if (retryCount < 0) throw new ArgumentOutOfRangeException(nameof(retryCount), retryCount, "should be >= 0");
if (medianFirstRetryDelay < TimeSpan.Zero)
throw new ArgumentOutOfRangeException(nameof(medianFirstRetryDelay), medianFirstRetryDelay,
"should be >= 0ms");

return (i, r, c) => Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay, retryCount).ToArray()[i - 1];
}
return new SleepDurationProvider(retryCount, Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay, retryCount));
}
}
}