From 3edf65cd0788c3835909932fc8f1682c46fbae56 Mon Sep 17 00:00:00 2001 From: "darya.bulanova" Date: Sun, 1 Nov 2020 00:29:50 +0300 Subject: [PATCH] refactor: make sleep duration provider variation --- .../CircuitBreakerPolicyTests.cs | 14 ++-- .../DSL/HttpClientWrapperBuilder.cs | 2 +- .../RetryPolicyTests.cs | 17 +++-- .../TimeoutPolicyTests.cs | 28 ++++---- .../Defaults.cs | 1 + .../HttpClientBuilderExtensions.cs | 5 +- .../ResiliencePoliciesSettings.cs | 4 +- .../IRetryPolicySettings.cs | 3 +- .../ISleepDurationProvider.cs | 10 +++ .../JitterRetryPolicySettings.cs | 42 ----------- .../RetryPolicySettings.cs | 28 ++++++++ .../SimpleRetryPolicySettings.cs | 33 --------- .../SleepDurationProvider.cs | 71 +++++++++++++++++++ 13 files changed, 144 insertions(+), 114 deletions(-) create mode 100644 src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/ISleepDurationProvider.cs delete mode 100644 src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/JitterRetryPolicySettings.cs create mode 100644 src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/RetryPolicySettings.cs delete mode 100644 src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/SimpleRetryPolicySettings.cs create mode 100644 src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/SleepDurationProvider.cs diff --git a/src/Dodo.HttpClient.ResiliencePolicies.Tests/CircuitBreakerPolicyTests.cs b/src/Dodo.HttpClient.ResiliencePolicies.Tests/CircuitBreakerPolicyTests.cs index 8489c38..d3316c3 100644 --- a/src/Dodo.HttpClient.ResiliencePolicies.Tests/CircuitBreakerPolicyTests.cs +++ b/src/Dodo.HttpClient.ResiliencePolicies.Tests/CircuitBreakerPolicyTests.cs @@ -17,10 +17,9 @@ public void Should_break_after_4_concurrent_calls() { const int retryCount = 5; const int minimumThroughput = 2; - var retrySettings = new SimpleRetryPolicySettings(retryCount) - { - SleepDurationProvider = i => TimeSpan.FromMilliseconds(50) - }; + var retrySettings = new RetryPolicySettings( + SleepDurationProvider.Constant(retryCount, TimeSpan.FromMilliseconds(50))); + var wrapper = Create.HttpClientWrapperWrapperBuilder .WithStatusCode(HttpStatusCode.ServiceUnavailable) .WithTimeoutOverall(TimeSpan.FromSeconds(5)) @@ -40,10 +39,9 @@ public async Task Should_Open_Circuit_Breaker_for_RU_and_do_not_affect_EE() { const int retryCount = 5; const int minimumThroughput = 2; - var retrySettings = new SimpleRetryPolicySettings(retryCount) - { - SleepDurationProvider = i => TimeSpan.FromMilliseconds(50) - }; + var retrySettings = new RetryPolicySettings( + SleepDurationProvider.Constant(retryCount, TimeSpan.FromMilliseconds(50))); + var circuitBreakerSettings = BuildCircuitBreakerSettings(minimumThroughput); circuitBreakerSettings.IsHostSpecificOn = true; var wrapper = Create.HttpClientWrapperWrapperBuilder diff --git a/src/Dodo.HttpClient.ResiliencePolicies.Tests/DSL/HttpClientWrapperBuilder.cs b/src/Dodo.HttpClient.ResiliencePolicies.Tests/DSL/HttpClientWrapperBuilder.cs index 306b810..cc05e75 100644 --- a/src/Dodo.HttpClient.ResiliencePolicies.Tests/DSL/HttpClientWrapperBuilder.cs +++ b/src/Dodo.HttpClient.ResiliencePolicies.Tests/DSL/HttpClientWrapperBuilder.cs @@ -98,7 +98,7 @@ private ResiliencePoliciesSettings BuildClientSettings() { OverallTimeoutPolicySettings = new OverallTimeoutPolicySettings { Timeout = _timeoutOverall }, TimeoutPerTryPolicySettings = new TimeoutPerTryPolicySettings { Timeout = _timeoutPerTry }, - RetrySettings = _retrySettings ?? new JitterRetryPolicySettings(), + RetrySettings = _retrySettings ?? new RetryPolicySettings(), CircuitBreakerSettings = defaultCircuitBreakerSettings }; } diff --git a/src/Dodo.HttpClient.ResiliencePolicies.Tests/RetryPolicyTests.cs b/src/Dodo.HttpClient.ResiliencePolicies.Tests/RetryPolicyTests.cs index 887edb3..bc62dc1 100644 --- a/src/Dodo.HttpClient.ResiliencePolicies.Tests/RetryPolicyTests.cs +++ b/src/Dodo.HttpClient.ResiliencePolicies.Tests/RetryPolicyTests.cs @@ -18,7 +18,8 @@ public class RetryPolicyTests public async Task Should_retry_3_times_when_client_returns_503() { const int retryCount = 3; - var retrySettings = new SimpleRetryPolicySettings(retryCount); + var retrySettings = new RetryPolicySettings( + SleepDurationProvider.Constant(retryCount, TimeSpan.FromMilliseconds(1))); var wrapper = Create.HttpClientWrapperWrapperBuilder .WithStatusCode(HttpStatusCode.ServiceUnavailable) @@ -35,9 +36,9 @@ public async Task Should_retry_3_times_when_client_returns_503() public async Task Should_retry_6_times_for_two_threads_when_client_returns_503() { const int retryCount = 3; - var retrySettings = - new JitterRetryPolicySettings(retryCount, - medianFirstRetryDelay: TimeSpan.FromMilliseconds(50)); + var retrySettings = new RetryPolicySettings( + SleepDurationProvider.Jitter(retryCount, + medianFirstRetryDelay: TimeSpan.FromMilliseconds(50))); var wrapper = Create.HttpClientWrapperWrapperBuilder .WithStatusCode(HttpStatusCode.ServiceUnavailable) .WithRetrySettings(retrySettings) @@ -54,8 +55,8 @@ public async Task Should_separately_distribute_retry_attempts_for_multiple_tasks { const int retryCount = 3; var retryAttempts = new Dictionary>(); - var retrySettings = new JitterRetryPolicySettings(retryCount, - medianFirstRetryDelay: TimeSpan.FromMilliseconds(50) ) + var retrySettings = new RetryPolicySettings(SleepDurationProvider.Jitter(retryCount, + medianFirstRetryDelay: TimeSpan.FromMilliseconds(50) )) { OnRetry = BuildOnRetryAction(retryAttempts) }; @@ -74,7 +75,9 @@ public async Task Should_separately_distribute_retry_attempts_for_multiple_tasks public async Task Should_retry_when_client_returns_500() { const int retryCount = 3; - var retrySettings = new SimpleRetryPolicySettings(retryCount); + var retrySettings = new RetryPolicySettings( + SleepDurationProvider.Constant(retryCount, TimeSpan.FromMilliseconds(1))); + var wrapper = Create.HttpClientWrapperWrapperBuilder .WithStatusCode(HttpStatusCode.InternalServerError) .WithRetrySettings(retrySettings) diff --git a/src/Dodo.HttpClient.ResiliencePolicies.Tests/TimeoutPolicyTests.cs b/src/Dodo.HttpClient.ResiliencePolicies.Tests/TimeoutPolicyTests.cs index ed11208..0659e6c 100644 --- a/src/Dodo.HttpClient.ResiliencePolicies.Tests/TimeoutPolicyTests.cs +++ b/src/Dodo.HttpClient.ResiliencePolicies.Tests/TimeoutPolicyTests.cs @@ -15,10 +15,9 @@ public class TimeoutPolicyTests public void Should_retry_5_times_200_status_code_because_of_per_try_timeout() { const int retryCount = 5; - var retrySettings = new SimpleRetryPolicySettings(retryCount) - { - SleepDurationProvider = i => TimeSpan.FromMilliseconds(200) - }; + var retrySettings = new RetryPolicySettings( + SleepDurationProvider.Constant(retryCount, TimeSpan.FromMilliseconds(200))); + var wrapper = Create.HttpClientWrapperWrapperBuilder .WithStatusCode(HttpStatusCode.OK) .WithResponseLatency(TimeSpan.FromMilliseconds(200)) @@ -51,10 +50,9 @@ public void Should_fail_on_HttpClient_timeout() public void Should_fail_on_HttpClient_timeout_with_retry() { const int retryCount = 5; - var retrySettings = new SimpleRetryPolicySettings(retryCount) - { - SleepDurationProvider = i => TimeSpan.FromMilliseconds(1) - }; + var retrySettings = new RetryPolicySettings( + SleepDurationProvider.Constant(retryCount, TimeSpan.FromMilliseconds(1))); + var wrapper = Create.HttpClientWrapperWrapperBuilder .WithStatusCode(HttpStatusCode.ServiceUnavailable) .WithResponseLatency(TimeSpan.FromMilliseconds(50)) @@ -88,10 +86,9 @@ public void Should_catchTimeout_1_times_because_of_overall_timeout_less_than_per var overallTimeout = TimeSpan.FromMilliseconds(100); var perTryTimeout = TimeSpan.FromMilliseconds(200); - var retrySettings = new SimpleRetryPolicySettings(retryCount) - { - SleepDurationProvider = i => TimeSpan.FromMilliseconds(200) - }; + var retrySettings =new RetryPolicySettings( + SleepDurationProvider.Constant(retryCount, TimeSpan.FromMilliseconds(200))); + var wrapper = Create.HttpClientWrapperWrapperBuilder .WithStatusCode(HttpStatusCode.OK) .WithResponseLatency(TimeSpan.FromMilliseconds(300)) @@ -113,10 +110,9 @@ public void When_overall_timeout_greated_than_summ_perTrials_Should_retry_5_time var perTryTimeout = TimeSpan.FromMilliseconds(100); var overallTimeout = TimeSpan.FromSeconds(2); - var retrySettings = new SimpleRetryPolicySettings(retryCount) - { - SleepDurationProvider = i => TimeSpan.FromMilliseconds(200) - }; + var retrySettings = new RetryPolicySettings( + SleepDurationProvider.Constant(retryCount, TimeSpan.FromMilliseconds(200))); + var wrapper = Create.HttpClientWrapperWrapperBuilder .WithStatusCode(HttpStatusCode.OK) .WithResponseLatency(TimeSpan.FromMilliseconds(200)) diff --git a/src/Dodo.HttpClient.ResiliencePolicies/Defaults.cs b/src/Dodo.HttpClient.ResiliencePolicies/Defaults.cs index cc20030..25407b1 100644 --- a/src/Dodo.HttpClient.ResiliencePolicies/Defaults.cs +++ b/src/Dodo.HttpClient.ResiliencePolicies/Defaults.cs @@ -13,6 +13,7 @@ public static class Timeout public static class Retry { public const int RetryCount = 2; + public const int InitialDelayMilliseconds = 20; public const int MedianFirstRetryDelayInMilliseconds = 2000; } diff --git a/src/Dodo.HttpClient.ResiliencePolicies/HttpClientBuilderExtensions.cs b/src/Dodo.HttpClient.ResiliencePolicies/HttpClientBuilderExtensions.cs index 95dd1e0..caf9d99 100644 --- a/src/Dodo.HttpClient.ResiliencePolicies/HttpClientBuilderExtensions.cs +++ b/src/Dodo.HttpClient.ResiliencePolicies/HttpClientBuilderExtensions.cs @@ -71,15 +71,14 @@ public static IHttpClientBuilder AddJsonClient() .WaitAndRetryAsync( - settings.RetryCount, - settings.SleepDurationProvider, + settings.SleepDurationProvider.SleepFunction, settings.OnRetry)); } diff --git a/src/Dodo.HttpClient.ResiliencePolicies/ResiliencePoliciesSettings.cs b/src/Dodo.HttpClient.ResiliencePolicies/ResiliencePoliciesSettings.cs index 7502e13..5bbdeab 100644 --- a/src/Dodo.HttpClient.ResiliencePolicies/ResiliencePoliciesSettings.cs +++ b/src/Dodo.HttpClient.ResiliencePolicies/ResiliencePoliciesSettings.cs @@ -18,8 +18,8 @@ public ResiliencePoliciesSettings() { OverallTimeoutPolicySettings = new OverallTimeoutPolicySettings(); TimeoutPerTryPolicySettings = new TimeoutPerTryPolicySettings(); - RetrySettings = new SimpleRetryPolicySettings(); - CircuitBreakerSettings = new CircuitBreakerSettings.CircuitBreakerPolicySettings(); + RetrySettings = new RetryPolicySettings(); + CircuitBreakerSettings = new CircuitBreakerPolicySettings(); } } } diff --git a/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/IRetryPolicySettings.cs b/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/IRetryPolicySettings.cs index c104d55..66956d6 100644 --- a/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/IRetryPolicySettings.cs +++ b/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/IRetryPolicySettings.cs @@ -6,8 +6,7 @@ namespace Dodo.HttpClientResiliencePolicies.RetrySettings { public interface IRetryPolicySettings { - public int RetryCount { get; } - public Func SleepDurationProvider { get; } + public ISleepDurationProvider SleepDurationProvider { get; } public Action, TimeSpan> OnRetry { get; set; } } } diff --git a/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/ISleepDurationProvider.cs b/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/ISleepDurationProvider.cs new file mode 100644 index 0000000..cd1f43e --- /dev/null +++ b/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/ISleepDurationProvider.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace Dodo.HttpClientResiliencePolicies.RetrySettings +{ + public interface ISleepDurationProvider + { + IEnumerable SleepFunction { get; } + } +} diff --git a/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/JitterRetryPolicySettings.cs b/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/JitterRetryPolicySettings.cs deleted file mode 100644 index 56b3d74..0000000 --- a/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/JitterRetryPolicySettings.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Linq; -using System.Net.Http; -using Polly; -using Polly.Contrib.WaitAndRetry; - -namespace Dodo.HttpClientResiliencePolicies.RetrySettings -{ - public class JitterRetryPolicySettings : IRetryPolicySettings - { - public int RetryCount { get; } - public Func SleepDurationProvider { get; } - public Action, TimeSpan> OnRetry { get; set; } - - public JitterRetryPolicySettings() - : this(Defaults.Retry.RetryCount) - { - } - - public JitterRetryPolicySettings(int retryCount) - : this(retryCount, - TimeSpan.FromMilliseconds(Defaults.Retry.MedianFirstRetryDelayInMilliseconds)) - { - } - - public JitterRetryPolicySettings( - int retryCount, - TimeSpan medianFirstRetryDelay) - { - RetryCount = retryCount; - SleepDurationProvider = _defaultSleepDurationProvider(RetryCount, medianFirstRetryDelay); - OnRetry = _doNothingOnRetry; - } - - // i - retry attempt - private static readonly Func> _defaultSleepDurationProvider = - (retryCount, medianFirstRetryDelay) => i => - Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay, retryCount).ToArray()[i - 1]; - - private static readonly Action, TimeSpan> _doNothingOnRetry = (_, __) => { }; - } -} diff --git a/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/RetryPolicySettings.cs b/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/RetryPolicySettings.cs new file mode 100644 index 0000000..0835501 --- /dev/null +++ b/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/RetryPolicySettings.cs @@ -0,0 +1,28 @@ +using System; +using System.Net.Http; +using Polly; + +namespace Dodo.HttpClientResiliencePolicies.RetrySettings +{ + public class RetryPolicySettings : IRetryPolicySettings + { + public ISleepDurationProvider SleepDurationProvider { get; } + public Action, TimeSpan> OnRetry { get; set; } + + public RetryPolicySettings() + : this(RetrySettings.SleepDurationProvider.Jitter( + Defaults.Retry.RetryCount, + TimeSpan.FromMilliseconds(Defaults.Retry.MedianFirstRetryDelayInMilliseconds))) + { + } + + public RetryPolicySettings( + ISleepDurationProvider sleepDurationProvider) + { + SleepDurationProvider = sleepDurationProvider; + OnRetry = _doNothingOnRetry; + } + + private static readonly Action, TimeSpan> _doNothingOnRetry = (_, __) => { }; + } +} diff --git a/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/SimpleRetryPolicySettings.cs b/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/SimpleRetryPolicySettings.cs deleted file mode 100644 index 918b569..0000000 --- a/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/SimpleRetryPolicySettings.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Net.Http; -using Polly; - -namespace Dodo.HttpClientResiliencePolicies.RetrySettings -{ - public class SimpleRetryPolicySettings : IRetryPolicySettings - { - public int RetryCount { get; } - public Func SleepDurationProvider { get; set; } - public Action, TimeSpan> OnRetry { get; set; } - - public SimpleRetryPolicySettings() - : this(Defaults.Retry.RetryCount) - { - RetryCount = Defaults.Retry.RetryCount; - SleepDurationProvider = _defaultSleepDurationProvider; - OnRetry = _doNothingOnRetry; - } - - public SimpleRetryPolicySettings(int retryCount) - { - RetryCount = retryCount; - SleepDurationProvider = _defaultSleepDurationProvider; - OnRetry = _doNothingOnRetry; - } - - private static readonly Func _defaultSleepDurationProvider = - i => TimeSpan.FromMilliseconds(20 * Math.Pow(2, i)); - - private static readonly Action, TimeSpan> _doNothingOnRetry = (_, __) => { }; - } -} diff --git a/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/SleepDurationProvider.cs b/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/SleepDurationProvider.cs new file mode 100644 index 0000000..424d1e1 --- /dev/null +++ b/src/Dodo.HttpClient.ResiliencePolicies/RetryPolicySettings/SleepDurationProvider.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using Polly.Contrib.WaitAndRetry; + +namespace Dodo.HttpClientResiliencePolicies.RetrySettings +{ + public sealed class SleepDurationProvider : ISleepDurationProvider + { + public IEnumerable SleepFunction { get; } + + private SleepDurationProvider(IEnumerable sleepFunction) + { + if (sleepFunction == null) + throw new ArgumentNullException(nameof(sleepFunction)); + + SleepFunction = sleepFunction; + } + + public static ISleepDurationProvider Exponential(int retryCount) + { + return Exponential(retryCount, TimeSpan.FromMilliseconds(Defaults.Retry.InitialDelayMilliseconds)); + } + + public static ISleepDurationProvider 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(Backoff.ExponentialBackoff(initialDelay, retryCount)); + } + + public static ISleepDurationProvider Linear(int retryCount) + { + return Linear(retryCount, TimeSpan.FromMilliseconds(Defaults.Retry.InitialDelayMilliseconds)); + } + + public static ISleepDurationProvider 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 new SleepDurationProvider(Backoff.LinearBackoff(initialDelay, retryCount)); + } + + public static ISleepDurationProvider Jitter(int retryCount) + { + return Jitter(retryCount, TimeSpan.FromMilliseconds(Defaults.Retry.MedianFirstRetryDelayInMilliseconds)); + } + + public static ISleepDurationProvider 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 new SleepDurationProvider(Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay, retryCount)); + } + + public static ISleepDurationProvider Constant(int retryCount) + { + return Constant(retryCount, TimeSpan.FromMilliseconds(Defaults.Retry.InitialDelayMilliseconds)); + } + + public static ISleepDurationProvider 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"); + + return new SleepDurationProvider(Backoff.ConstantBackoff(delay, retryCount)); + } + } +}