From 02ba73e9f8c8aacc2ccccb03d3d6edb2e768ff24 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Thu, 31 Aug 2023 14:35:16 +0200 Subject: [PATCH 1/4] Upgrade to beta.9 --- .../HttpClientFactory.cs | 2 +- .../ResilienceEnrichmentBenchmark.cs | 18 +-- eng/packages/General.props | 8 +- eng/packages/TestOnly.props | 2 +- .../Hedging/HedgingEndpointOptions.cs | 6 +- .../HttpClientBuilderExtensions.Hedging.cs | 20 ++-- .../HttpStandardHedgingResilienceOptions.cs | 8 +- ...mes.cs => StandardHedgingPipelineNames.cs} | 2 +- ...HedgingResilienceOptionsCustomValidator.cs | 4 +- ...StandardHedgingHandlerBuilderExtensions.cs | 30 ++--- .../Internal/ValidationHelper.cs | 16 +-- .../Microsoft.Extensions.Http.Resilience.json | 44 +++---- .../Polly/HttpRetryStrategyOptions.cs | 11 +- .../HttpClientBuilderExtensions.Resilience.cs | 90 +++++++-------- ...entBuilderExtensions.StandardResilience.cs | 18 +-- ...ttpResiliencePipelineBuilderExtensions.cs} | 18 +-- ...ndardResilienceBuilderBuilderExtensions.cs | 40 +++---- .../HttpStandardResilienceOptions.cs | 24 ++-- ...r.cs => IHttpResiliencePipelineBuilder.cs} | 8 +- ...IHttpStandardResiliencePipelineBuilder.cs} | 8 +- ...r.cs => ByAuthorityPipelineKeyProvider.cs} | 22 ++-- ...egyKeyOptions.cs => PipelineKeyOptions.cs} | 4 +- .../Internal/PipelineKeyProviderHelper.cs | 39 +++++++ ...egyNameHelper.cs => PipelineNameHelper.cs} | 4 +- .../Resilience/Internal/ResilienceHandler.cs | 10 +- ...ategyNames.cs => StandardPipelineNames.cs} | 2 +- .../Internal/StrategyKeyProviderHelper.cs | 39 ------- ...tandardResilienceOptionsCustomValidator.cs | 2 +- .../Resilience/ResilienceHandlerContext.cs | 16 +-- .../Internal/FailureEventMetricsOptions.cs | 5 + ...icher.cs => ResilienceMeteringEnricher.cs} | 28 ++--- .../ResilienceServiceCollectionExtensions.cs | 17 +-- .../Hedging/HedgingTests.cs | 4 +- ...ngResilienceOptionsCustomValidatorTests.cs | 10 +- .../Hedging/StandardHedgingTests.cs | 36 +++--- .../RequestMessageSnapshotStrategyTests.cs | 8 +- .../Internal/ValidationHelperTests.cs | 19 +-- .../HttpCircuitBreakerStrategyOptionsTests.cs | 8 +- .../Polly/HttpRetryStrategyOptionsTests.cs | 36 +++--- ...ClientBuilderExtensionsTests.BySelector.cs | 50 ++++---- ...ClientBuilderExtensionsTests.Resilience.cs | 86 +++++++------- ...tpClientBuilderExtensionsTests.Standard.cs | 27 +++-- ...rdResilienceOptionsCustomValidatorTests.cs | 9 +- ...elperTest.cs => PipelineNameHelperTest.cs} | 5 +- .../Resilience/ResilienceHandlerTest.cs | 10 +- .../Routing/RoutingResilienceStrategyTests.cs | 8 +- .../ResilienceMeteringEnricherTests.cs | 102 +++++++++++++++++ ...ilienceServiceCollectionExtensionsTests.cs | 108 +++--------------- 48 files changed, 559 insertions(+), 532 deletions(-) rename src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/Internals/{StandardHedgingStrategyNames.cs => StandardHedgingPipelineNames.cs} (91%) rename src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/{HttpResilienceStrategyBuilderExtensions.cs => HttpResiliencePipelineBuilderExtensions.cs} (64%) rename src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/{IHttpResilienceStrategyBuilder.cs => IHttpResiliencePipelineBuilder.cs} (67%) rename src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/{IHttpStandardResilienceStrategyBuilder.cs => IHttpStandardResiliencePipelineBuilder.cs} (67%) rename src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/{ByAuthorityStrategyKeyProvider.cs => ByAuthorityPipelineKeyProvider.cs} (63%) rename src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/{StrategyKeyOptions.cs => PipelineKeyOptions.cs} (76%) create mode 100644 src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/PipelineKeyProviderHelper.cs rename src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/{StrategyNameHelper.cs => PipelineNameHelper.cs} (54%) rename src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/{StandardStrategyNames.cs => StandardPipelineNames.cs} (92%) delete mode 100644 src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/StrategyKeyProviderHelper.cs rename src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/{ResilienceEnricher.cs => ResilienceMeteringEnricher.cs} (68%) rename test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/{StrategyNameHelperTest.cs => PipelineNameHelperTest.cs} (78%) create mode 100644 test/Libraries/Microsoft.Extensions.Resilience.Tests/Resilience/ResilienceMeteringEnricherTests.cs diff --git a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HttpClientFactory.cs b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HttpClientFactory.cs index 1d0452f6ccf..f9ce16987b1 100644 --- a/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HttpClientFactory.cs +++ b/bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HttpClientFactory.cs @@ -69,7 +69,7 @@ public static ServiceProvider InitializeServiceProvider(params HedgingClientType private static void AddHedging(this IServiceCollection services, HedgingClientType clientType) { var clientBuilder = services.AddHttpClient(clientType.ToString(), client => client.Timeout = Timeout.InfiniteTimeSpan); - var hedgingBuilder = clientBuilder.AddStandardHedgingHandler().SelectStrategyByAuthority(SimpleClassifications.PublicData); + var hedgingBuilder = clientBuilder.AddStandardHedgingHandler().SelectPipelineByAuthority(SimpleClassifications.PublicData); _ = clientBuilder.AddHttpMessageHandler(); if (clientType.HasFlag(HedgingClientType.NoRoutes)) diff --git a/bench/Libraries/Microsoft.Extensions.Resilience.PerformanceTests/ResilienceEnrichmentBenchmark.cs b/bench/Libraries/Microsoft.Extensions.Resilience.PerformanceTests/ResilienceEnrichmentBenchmark.cs index 9d64972d56b..0ada570bc67 100644 --- a/bench/Libraries/Microsoft.Extensions.Resilience.PerformanceTests/ResilienceEnrichmentBenchmark.cs +++ b/bench/Libraries/Microsoft.Extensions.Resilience.PerformanceTests/ResilienceEnrichmentBenchmark.cs @@ -16,15 +16,15 @@ namespace Microsoft.Extensions.Resilience.Bench; public class ResilienceEnrichmentBenchmark { private MeterListener? _listener; - private ResilienceStrategy? _strategy; - private ResilienceStrategy? _strategyEnriched; + private ResiliencePipeline? _pipeline; + private ResiliencePipeline? _pipelineEnriched; [GlobalSetup] public void GlobalSetup() { _listener = MeteringUtil.ListenPollyMetrics(); - _strategy = CreateResilienceStrategy(_ => { }); - _strategyEnriched = CreateResilienceStrategy(services => + _pipeline = CreateResiliencePipeline(_ => { }); + _pipelineEnriched = CreateResiliencePipeline(services => { services.AddResilienceEnrichment(); services.ConfigureFailureResultContext(res => FailureResultContext.Create("dummy", "dummy", "dummy")); @@ -35,21 +35,21 @@ public void GlobalSetup() public void Cleanup() => _listener?.Dispose(); [Benchmark(Baseline = true)] - public void ReportTelemetry() => _strategy!.Execute(() => "dummy-result"); + public void ReportTelemetry() => _pipeline!.Execute(() => "dummy-result"); [Benchmark] - public void ReportTelemetry_Enriched() => _strategyEnriched!.Execute(() => "dummy-result"); + public void ReportTelemetry_Enriched() => _pipelineEnriched!.Execute(() => "dummy-result"); - private static ResilienceStrategy CreateResilienceStrategy(Action configure) + private static ResiliencePipeline CreateResiliencePipeline(Action configure) { var services = new ServiceCollection(); services.AddLogging(); services.AddExceptionSummarizer(); - services.AddResilienceStrategy("my-strategy", builder => builder.AddStrategy(context => new DummyStrategy(context.Telemetry), new DummyOptions())); + services.AddResiliencePipeline("my-pipeline", builder => builder.AddStrategy(context => new DummyStrategy(context.Telemetry), new DummyOptions())); services.AddLogging(); configure(services); - return services.BuildServiceProvider().GetRequiredService>().GetStrategy("my-strategy"); + return services.BuildServiceProvider().GetRequiredService>().GetPipeline("my-pipeline"); } private class DummyStrategy : ResilienceStrategy diff --git a/eng/packages/General.props b/eng/packages/General.props index cc12f3c2642..4c0f51a09d8 100644 --- a/eng/packages/General.props +++ b/eng/packages/General.props @@ -38,10 +38,10 @@ - - - - + + + + diff --git a/eng/packages/TestOnly.props b/eng/packages/TestOnly.props index 2f22fe46328..59c7a510af4 100644 --- a/eng/packages/TestOnly.props +++ b/eng/packages/TestOnly.props @@ -8,7 +8,7 @@ - + diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HedgingEndpointOptions.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HedgingEndpointOptions.cs index 9a8f288fbdf..e212ae628c7 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HedgingEndpointOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HedgingEndpointOptions.cs @@ -27,7 +27,7 @@ public class HedgingEndpointOptions [ValidateObjectMembers] public HttpRateLimiterStrategyOptions RateLimiterOptions { get; set; } = new HttpRateLimiterStrategyOptions { - Name = StandardHedgingStrategyNames.RateLimiter + Name = StandardHedgingPipelineNames.RateLimiter }; /// @@ -40,7 +40,7 @@ public class HedgingEndpointOptions [ValidateObjectMembers] public HttpCircuitBreakerStrategyOptions CircuitBreakerOptions { get; set; } = new HttpCircuitBreakerStrategyOptions { - Name = StandardHedgingStrategyNames.CircuitBreaker + Name = StandardHedgingPipelineNames.CircuitBreaker }; /// @@ -55,6 +55,6 @@ public class HedgingEndpointOptions public HttpTimeoutStrategyOptions TimeoutOptions { get; set; } = new() { Timeout = TimeSpan.FromSeconds(10), - Name = StandardHedgingStrategyNames.AttemptTimeout + Name = StandardHedgingPipelineNames.AttemptTimeout }; } diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpClientBuilderExtensions.Hedging.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpClientBuilderExtensions.Hedging.cs index bfa4b633bf7..2a2e7a10752 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpClientBuilderExtensions.Hedging.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpClientBuilderExtensions.Hedging.cs @@ -34,7 +34,7 @@ public static partial class HttpClientBuilderExtensions /// The standard hedging uses a pool of circuit breakers to ensure that unhealthy endpoints are not hedged against. /// By default, the selection from pool is based on the URL Authority (scheme + host + port). /// It is recommended that you configure the way the strategies are selected by calling - /// + /// /// extensions. /// /// See for more details about the used resilience strategies. @@ -63,7 +63,7 @@ public static IStandardHedgingHandlerBuilder AddStandardHedgingHandler(this IHtt /// The standard hedging uses a pool of circuit breakers to ensure that unhealthy endpoints are not hedged against. /// By default, the selection from pool is based on the URL Authority (scheme + host + port). /// It is recommended that you configure the way the strategies are selected by calling - /// + /// /// extensions. /// /// See for more details about the used resilience strategies. @@ -82,7 +82,7 @@ public static IStandardHedgingHandlerBuilder AddStandardHedgingHandler(this IHtt _ = builder.Services.AddOptionsWithValidateOnStart(optionsName); _ = builder.Services.PostConfigure(optionsName, options => { - options.HedgingOptions.HedgingActionGenerator = args => + options.HedgingOptions.ActionGenerator = args => { if (!args.PrimaryContext.Properties.TryGetValue(ResilienceKeys.RequestSnapshot, out var snapshot)) { @@ -94,9 +94,9 @@ public static IStandardHedgingHandlerBuilder AddStandardHedgingHandler(this IHtt // replace the request message args.ActionContext.Properties.Set(ResilienceKeys.RequestMessage, requestMessage); - if (args.PrimaryContext.Properties.TryGetValue(ResilienceKeys.RoutingStrategy, out var routingStrategy)) + if (args.PrimaryContext.Properties.TryGetValue(ResilienceKeys.RoutingStrategy, out var routingPipeline)) { - if (!routingStrategy.TryGetNextRoute(out var route)) + if (!routingPipeline.TryGetNextRoute(out var route)) { // no routes left, stop hedging return null; @@ -117,8 +117,8 @@ public static IStandardHedgingHandlerBuilder AddStandardHedgingHandler(this IHtt var routingOptions = context.GetOptions(routingBuilder.Name); _ = builder - .AddStrategy(new RoutingResilienceStrategy(routingOptions.RoutingStrategyProvider)) - .AddStrategy(new RequestMessageSnapshotStrategy()) + .AddStrategy(_ => new RoutingResilienceStrategy(routingOptions.RoutingStrategyProvider), new EmptyResilienceStrategyOptions()) + .AddStrategy(_ => new RequestMessageSnapshotStrategy(), new EmptyResilienceStrategyOptions()) .AddTimeout(options.TotalRequestTimeoutOptions) .AddHedging(options.HedgingOptions); }); @@ -136,7 +136,7 @@ public static IStandardHedgingHandlerBuilder AddStandardHedgingHandler(this IHtt .AddCircuitBreaker(options.EndpointOptions.CircuitBreakerOptions) .AddTimeout(options.EndpointOptions.TimeoutOptions); }) - .SelectStrategyByAuthority(DataClassification.Unknown); + .SelectPipelineByAuthority(DataClassification.Unknown); return new StandardHedgingHandlerBuilder(builder.Name, builder.Services, routingBuilder); } @@ -145,4 +145,8 @@ private record StandardHedgingHandlerBuilder( string Name, IServiceCollection Services, IRoutingStrategyBuilder RoutingStrategyBuilder) : IStandardHedgingHandlerBuilder; + + private sealed class EmptyResilienceStrategyOptions : ResilienceStrategyOptions + { + } } diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpStandardHedgingResilienceOptions.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpStandardHedgingResilienceOptions.cs index 57bc8c16782..6d5120aaf92 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpStandardHedgingResilienceOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpStandardHedgingResilienceOptions.cs @@ -29,8 +29,8 @@ namespace Microsoft.Extensions.Http.Resilience; /// /// /// The last three strategies are assigned to each individual endpoint. The selection of endpoint can be customized by -/// or -/// extensions. +/// or +/// extensions. /// /// By default, the endpoint is selected by authority (scheme + host + port). /// @@ -48,7 +48,7 @@ public class HttpStandardHedgingResilienceOptions [ValidateObjectMembers] public HttpTimeoutStrategyOptions TotalRequestTimeoutOptions { get; set; } = new HttpTimeoutStrategyOptions { - Name = StandardHedgingStrategyNames.TotalRequestTimeout + Name = StandardHedgingPipelineNames.TotalRequestTimeout }; /// @@ -61,7 +61,7 @@ public class HttpStandardHedgingResilienceOptions [ValidateObjectMembers] public HttpHedgingStrategyOptions HedgingOptions { get; set; } = new HttpHedgingStrategyOptions { - Name = StandardHedgingStrategyNames.Hedging + Name = StandardHedgingPipelineNames.Hedging }; /// diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/Internals/StandardHedgingStrategyNames.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/Internals/StandardHedgingPipelineNames.cs similarity index 91% rename from src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/Internals/StandardHedgingStrategyNames.cs rename to src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/Internals/StandardHedgingPipelineNames.cs index b4dac845036..f466da6d540 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/Internals/StandardHedgingStrategyNames.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/Internals/StandardHedgingPipelineNames.cs @@ -3,7 +3,7 @@ namespace Microsoft.Extensions.Http.Resilience.Internal.Validators; -internal static class StandardHedgingStrategyNames +internal static class StandardHedgingPipelineNames { public const string CircuitBreaker = "StandardHedging-CircuitBreaker"; diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/Internals/Validators/HttpStandardHedgingResilienceOptionsCustomValidator.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/Internals/Validators/HttpStandardHedgingResilienceOptionsCustomValidator.cs index e591b3907d4..a19850d1fbe 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/Internals/Validators/HttpStandardHedgingResilienceOptionsCustomValidator.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/Internals/Validators/HttpStandardHedgingResilienceOptionsCustomValidator.cs @@ -31,9 +31,9 @@ public ValidateOptionsResult Validate(string? name, HttpStandardHedgingResilienc } // if generator is specified we cannot calculate the max hedging delay - if (options.HedgingOptions.HedgingDelayGenerator == null) + if (options.HedgingOptions.DelayGenerator == null) { - var maxHedgingDelay = TimeSpan.FromMilliseconds((options.HedgingOptions.MaxHedgedAttempts - 1) * options.HedgingOptions.HedgingDelay.TotalMilliseconds); + var maxHedgingDelay = TimeSpan.FromMilliseconds(options.HedgingOptions.MaxHedgedAttempts * options.HedgingOptions.Delay.TotalMilliseconds); // Stryker disable once Equality if (maxHedgingDelay > options.TotalRequestTimeoutOptions.Timeout) diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/StandardHedgingHandlerBuilderExtensions.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/StandardHedgingHandlerBuilderExtensions.cs index 45590188236..cc33fb4c863 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/StandardHedgingHandlerBuilderExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/StandardHedgingHandlerBuilderExtensions.cs @@ -22,9 +22,9 @@ namespace Microsoft.Extensions.Http.Resilience; public static class StandardHedgingHandlerBuilderExtensions { /// - /// Configures the for the standard hedging strategy. + /// Configures the for the standard hedging pipeline. /// - /// The strategy builder. + /// The pipeline builder. /// The section that the options will bind against. /// The same builder instance. [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(HttpStandardHedgingResilienceOptions))] @@ -43,9 +43,9 @@ public static IStandardHedgingHandlerBuilder Configure(this IStandardHedgingHand } /// - /// Configures the for the standard hedging strategy. + /// Configures the for the standard hedging pipeline. /// - /// The strategy builder. + /// The pipeline builder. /// The configure method. /// The same builder instance. public static IStandardHedgingHandlerBuilder Configure(this IStandardHedgingHandlerBuilder builder, Action configure) @@ -57,9 +57,9 @@ public static IStandardHedgingHandlerBuilder Configure(this IStandardHedgingHand } /// - /// Configures the for the standard hedging strategy. + /// Configures the for the standard hedging pipeline. /// - /// The strategy builder. + /// The pipeline builder. /// The configure method. /// The same builder instance. [Experimental(diagnosticId: Experiments.Resilience, UrlFormat = Experiments.UrlFormat)] @@ -74,38 +74,38 @@ public static IStandardHedgingHandlerBuilder Configure(this IStandardHedgingHand } /// - /// Instructs the underlying strategy builder to select the strategy instance by redacted authority (scheme + host + port). + /// Instructs the underlying pipeline builder to select the pipeline instance by redacted authority (scheme + host + port). /// /// The builder instance. /// The data class associated with the authority. /// The same builder instance. /// The authority is redacted using retrieved for . - public static IStandardHedgingHandlerBuilder SelectStrategyByAuthority(this IStandardHedgingHandlerBuilder builder, DataClassification classification) + public static IStandardHedgingHandlerBuilder SelectPipelineByAuthority(this IStandardHedgingHandlerBuilder builder, DataClassification classification) { _ = Throw.IfNull(builder); - var strategyName = StrategyNameHelper.GetName(builder.Name, HttpClientBuilderExtensions.StandardInnerHandlerPostfix); + var pipelineName = PipelineNameHelper.GetName(builder.Name, HttpClientBuilderExtensions.StandardInnerHandlerPostfix); - StrategyKeyProviderHelper.SelectStrategyByAuthority(builder.Services, strategyName, classification); + PipelineKeyProviderHelper.SelectPipelineByAuthority(builder.Services, pipelineName, classification); return builder; } /// - /// Instructs the underlying strategy builder to select the strategy instance by custom selector. + /// Instructs the underlying pipeline builder to select the pipeline instance by custom selector. /// /// The builder instance. /// The factory that returns key selector. /// The same builder instance. - /// The strategy key is used in metrics and logs, do not return any sensitive value. - public static IStandardHedgingHandlerBuilder SelectStrategyBy(this IStandardHedgingHandlerBuilder builder, Func> selectorFactory) + /// The pipeline key is used in metrics and logs, do not return any sensitive value. + public static IStandardHedgingHandlerBuilder SelectPipelineBy(this IStandardHedgingHandlerBuilder builder, Func> selectorFactory) { _ = Throw.IfNull(builder); _ = Throw.IfNull(selectorFactory); - var strategyName = StrategyNameHelper.GetName(builder.Name, HttpClientBuilderExtensions.StandardInnerHandlerPostfix); + var pipelineName = PipelineNameHelper.GetName(builder.Name, HttpClientBuilderExtensions.StandardInnerHandlerPostfix); - StrategyKeyProviderHelper.SelectStrategyBy(builder.Services, strategyName, selectorFactory); + PipelineKeyProviderHelper.SelectPipelineBy(builder.Services, pipelineName, selectorFactory); return builder; } diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Internal/ValidationHelper.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Internal/ValidationHelper.cs index 9fb91bbdc11..95952c77eb1 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Internal/ValidationHelper.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Internal/ValidationHelper.cs @@ -19,25 +19,21 @@ public static TimeSpan GetAggregatedDelay(RetryStrategyOptions options) try { var aggregatedDelay = TimeSpan.Zero; - var builder = new CompositeStrategyBuilder + new ResiliencePipelineBuilder().AddRetry(new() { - Randomizer = () => 1.0 // disable randomization so the output is always the same - }; - - builder.AddRetry(new() - { - RetryCount = options.RetryCount, - BaseDelay = options.BaseDelay, + MaxRetryAttempts = options.MaxRetryAttempts, + Delay = options.Delay, BackoffType = options.BackoffType, ShouldHandle = _ => PredicateResult.True, // always retry until all retries are exhausted - RetryDelayGenerator = args => + DelayGenerator = args => { // the delay hint is calculated for this attempt by the retry strategy - aggregatedDelay += args.Arguments.DelayHint; + aggregatedDelay += args.DelayHint; // return zero delay, so no waiting return new ValueTask(TimeSpan.Zero); }, + Randomizer = () => 1.0 // disable randomization so the output is always the same }) .Build() .Execute(static () => { }); // this executes all retries and we aggregate the delays immediately diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Microsoft.Extensions.Http.Resilience.json b/src/Libraries/Microsoft.Extensions.Http.Resilience/Microsoft.Extensions.Http.Resilience.json index 548524ec1f2..089d60e0f74 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Microsoft.Extensions.Http.Resilience.json +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Microsoft.Extensions.Http.Resilience.json @@ -82,11 +82,11 @@ "Stage": "Stable", "Methods": [ { - "Member": "static Microsoft.Extensions.Http.Resilience.IHttpResilienceStrategyBuilder Microsoft.Extensions.Http.Resilience.HttpClientBuilderExtensions.AddResilienceHandler(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, string strategyName, System.Action> configure);", + "Member": "static Microsoft.Extensions.Http.Resilience.IHttpResiliencePipelineBuilder Microsoft.Extensions.Http.Resilience.HttpClientBuilderExtensions.AddResilienceHandler(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, string pipelineName, System.Action> configure);", "Stage": "Stable" }, { - "Member": "static Microsoft.Extensions.Http.Resilience.IHttpResilienceStrategyBuilder Microsoft.Extensions.Http.Resilience.HttpClientBuilderExtensions.AddResilienceHandler(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, string strategyName, System.Action, Microsoft.Extensions.Http.Resilience.ResilienceHandlerContext> configure);", + "Member": "static Microsoft.Extensions.Http.Resilience.IHttpResiliencePipelineBuilder Microsoft.Extensions.Http.Resilience.HttpClientBuilderExtensions.AddResilienceHandler(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, string pipelineName, System.Action, Microsoft.Extensions.Http.Resilience.ResilienceHandlerContext> configure);", "Stage": "Stable" }, { @@ -98,15 +98,15 @@ "Stage": "Stable" }, { - "Member": "static Microsoft.Extensions.Http.Resilience.IHttpStandardResilienceStrategyBuilder Microsoft.Extensions.Http.Resilience.HttpClientBuilderExtensions.AddStandardResilienceHandler(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, Microsoft.Extensions.Configuration.IConfigurationSection section);", + "Member": "static Microsoft.Extensions.Http.Resilience.IHttpStandardResiliencePipelineBuilder Microsoft.Extensions.Http.Resilience.HttpClientBuilderExtensions.AddStandardResilienceHandler(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, Microsoft.Extensions.Configuration.IConfigurationSection section);", "Stage": "Stable" }, { - "Member": "static Microsoft.Extensions.Http.Resilience.IHttpStandardResilienceStrategyBuilder Microsoft.Extensions.Http.Resilience.HttpClientBuilderExtensions.AddStandardResilienceHandler(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Action configure);", + "Member": "static Microsoft.Extensions.Http.Resilience.IHttpStandardResiliencePipelineBuilder Microsoft.Extensions.Http.Resilience.HttpClientBuilderExtensions.AddStandardResilienceHandler(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder, System.Action configure);", "Stage": "Stable" }, { - "Member": "static Microsoft.Extensions.Http.Resilience.IHttpStandardResilienceStrategyBuilder Microsoft.Extensions.Http.Resilience.HttpClientBuilderExtensions.AddStandardResilienceHandler(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder);", + "Member": "static Microsoft.Extensions.Http.Resilience.IHttpStandardResiliencePipelineBuilder Microsoft.Extensions.Http.Resilience.HttpClientBuilderExtensions.AddStandardResilienceHandler(this Microsoft.Extensions.DependencyInjection.IHttpClientBuilder builder);", "Stage": "Stable" } ] @@ -238,15 +238,15 @@ ] }, { - "Type": "static class Microsoft.Extensions.Http.Resilience.HttpResilienceStrategyBuilderExtensions", + "Type": "static class Microsoft.Extensions.Http.Resilience.HttpResiliencePipelineBuilderExtensions", "Stage": "Stable", "Methods": [ { - "Member": "static Microsoft.Extensions.Http.Resilience.IHttpResilienceStrategyBuilder Microsoft.Extensions.Http.Resilience.HttpResilienceStrategyBuilderExtensions.SelectStrategyBy(this Microsoft.Extensions.Http.Resilience.IHttpResilienceStrategyBuilder builder, System.Func> selectorFactory);", + "Member": "static Microsoft.Extensions.Http.Resilience.IHttpResiliencePipelineBuilder Microsoft.Extensions.Http.Resilience.HttpResiliencePipelineBuilderExtensions.SelectPipelineBy(this Microsoft.Extensions.Http.Resilience.IHttpResiliencePipelineBuilder builder, System.Func> selectorFactory);", "Stage": "Stable" }, { - "Member": "static Microsoft.Extensions.Http.Resilience.IHttpResilienceStrategyBuilder Microsoft.Extensions.Http.Resilience.HttpResilienceStrategyBuilderExtensions.SelectStrategyByAuthority(this Microsoft.Extensions.Http.Resilience.IHttpResilienceStrategyBuilder builder, Microsoft.Extensions.Compliance.Classification.DataClassification classification);", + "Member": "static Microsoft.Extensions.Http.Resilience.IHttpResiliencePipelineBuilder Microsoft.Extensions.Http.Resilience.HttpResiliencePipelineBuilderExtensions.SelectPipelineByAuthority(this Microsoft.Extensions.Http.Resilience.IHttpResiliencePipelineBuilder builder, Microsoft.Extensions.Compliance.Classification.DataClassification classification);", "Stage": "Stable" } ] @@ -296,23 +296,23 @@ "Stage": "Stable", "Methods": [ { - "Member": "static Microsoft.Extensions.Http.Resilience.IHttpStandardResilienceStrategyBuilder Microsoft.Extensions.Http.Resilience.HttpStandardResilienceBuilderBuilderExtensions.Configure(this Microsoft.Extensions.Http.Resilience.IHttpStandardResilienceStrategyBuilder builder, Microsoft.Extensions.Configuration.IConfigurationSection section);", + "Member": "static Microsoft.Extensions.Http.Resilience.IHttpStandardResiliencePipelineBuilder Microsoft.Extensions.Http.Resilience.HttpStandardResilienceBuilderBuilderExtensions.Configure(this Microsoft.Extensions.Http.Resilience.IHttpStandardResiliencePipelineBuilder builder, Microsoft.Extensions.Configuration.IConfigurationSection section);", "Stage": "Stable" }, { - "Member": "static Microsoft.Extensions.Http.Resilience.IHttpStandardResilienceStrategyBuilder Microsoft.Extensions.Http.Resilience.HttpStandardResilienceBuilderBuilderExtensions.Configure(this Microsoft.Extensions.Http.Resilience.IHttpStandardResilienceStrategyBuilder builder, System.Action configure);", + "Member": "static Microsoft.Extensions.Http.Resilience.IHttpStandardResiliencePipelineBuilder Microsoft.Extensions.Http.Resilience.HttpStandardResilienceBuilderBuilderExtensions.Configure(this Microsoft.Extensions.Http.Resilience.IHttpStandardResiliencePipelineBuilder builder, System.Action configure);", "Stage": "Stable" }, { - "Member": "static Microsoft.Extensions.Http.Resilience.IHttpStandardResilienceStrategyBuilder Microsoft.Extensions.Http.Resilience.HttpStandardResilienceBuilderBuilderExtensions.Configure(this Microsoft.Extensions.Http.Resilience.IHttpStandardResilienceStrategyBuilder builder, System.Action configure);", + "Member": "static Microsoft.Extensions.Http.Resilience.IHttpStandardResiliencePipelineBuilder Microsoft.Extensions.Http.Resilience.HttpStandardResilienceBuilderBuilderExtensions.Configure(this Microsoft.Extensions.Http.Resilience.IHttpStandardResiliencePipelineBuilder builder, System.Action configure);", "Stage": "Experimental" }, { - "Member": "static Microsoft.Extensions.Http.Resilience.IHttpStandardResilienceStrategyBuilder Microsoft.Extensions.Http.Resilience.HttpStandardResilienceBuilderBuilderExtensions.SelectStrategyBy(this Microsoft.Extensions.Http.Resilience.IHttpStandardResilienceStrategyBuilder builder, System.Func> selectorFactory);", + "Member": "static Microsoft.Extensions.Http.Resilience.IHttpStandardResiliencePipelineBuilder Microsoft.Extensions.Http.Resilience.HttpStandardResilienceBuilderBuilderExtensions.SelectPipelineBy(this Microsoft.Extensions.Http.Resilience.IHttpStandardResiliencePipelineBuilder builder, System.Func> selectorFactory);", "Stage": "Stable" }, { - "Member": "static Microsoft.Extensions.Http.Resilience.IHttpStandardResilienceStrategyBuilder Microsoft.Extensions.Http.Resilience.HttpStandardResilienceBuilderBuilderExtensions.SelectStrategyByAuthority(this Microsoft.Extensions.Http.Resilience.IHttpStandardResilienceStrategyBuilder builder, Microsoft.Extensions.Compliance.Classification.DataClassification classification);", + "Member": "static Microsoft.Extensions.Http.Resilience.IHttpStandardResiliencePipelineBuilder Microsoft.Extensions.Http.Resilience.HttpStandardResilienceBuilderBuilderExtensions.SelectPipelineByAuthority(this Microsoft.Extensions.Http.Resilience.IHttpStandardResiliencePipelineBuilder builder, Microsoft.Extensions.Compliance.Classification.DataClassification classification);", "Stage": "Stable" } ] @@ -370,29 +370,29 @@ ] }, { - "Type": "interface Microsoft.Extensions.Http.Resilience.IHttpResilienceStrategyBuilder", + "Type": "interface Microsoft.Extensions.Http.Resilience.IHttpResiliencePipelineBuilder", "Stage": "Stable", "Properties": [ { - "Member": "Microsoft.Extensions.DependencyInjection.IServiceCollection Microsoft.Extensions.Http.Resilience.IHttpResilienceStrategyBuilder.Services { get; }", + "Member": "string Microsoft.Extensions.Http.Resilience.IHttpResiliencePipelineBuilder.PipelineName { get; }", "Stage": "Stable" }, { - "Member": "string Microsoft.Extensions.Http.Resilience.IHttpResilienceStrategyBuilder.StrategyName { get; }", + "Member": "Microsoft.Extensions.DependencyInjection.IServiceCollection Microsoft.Extensions.Http.Resilience.IHttpResiliencePipelineBuilder.Services { get; }", "Stage": "Stable" } ] }, { - "Type": "interface Microsoft.Extensions.Http.Resilience.IHttpStandardResilienceStrategyBuilder", + "Type": "interface Microsoft.Extensions.Http.Resilience.IHttpStandardResiliencePipelineBuilder", "Stage": "Stable", "Properties": [ { - "Member": "Microsoft.Extensions.DependencyInjection.IServiceCollection Microsoft.Extensions.Http.Resilience.IHttpStandardResilienceStrategyBuilder.Services { get; }", + "Member": "string Microsoft.Extensions.Http.Resilience.IHttpStandardResiliencePipelineBuilder.PipelineName { get; }", "Stage": "Stable" }, { - "Member": "string Microsoft.Extensions.Http.Resilience.IHttpStandardResilienceStrategyBuilder.StrategyName { get; }", + "Member": "Microsoft.Extensions.DependencyInjection.IServiceCollection Microsoft.Extensions.Http.Resilience.IHttpStandardResiliencePipelineBuilder.Services { get; }", "Stage": "Stable" } ] @@ -516,11 +516,11 @@ "Stage": "Experimental" }, { - "Member": "static Microsoft.Extensions.Http.Resilience.IStandardHedgingHandlerBuilder Microsoft.Extensions.Http.Resilience.StandardHedgingHandlerBuilderExtensions.SelectStrategyBy(this Microsoft.Extensions.Http.Resilience.IStandardHedgingHandlerBuilder builder, System.Func> selectorFactory);", + "Member": "static Microsoft.Extensions.Http.Resilience.IStandardHedgingHandlerBuilder Microsoft.Extensions.Http.Resilience.StandardHedgingHandlerBuilderExtensions.SelectPipelineBy(this Microsoft.Extensions.Http.Resilience.IStandardHedgingHandlerBuilder builder, System.Func> selectorFactory);", "Stage": "Stable" }, { - "Member": "static Microsoft.Extensions.Http.Resilience.IStandardHedgingHandlerBuilder Microsoft.Extensions.Http.Resilience.StandardHedgingHandlerBuilderExtensions.SelectStrategyByAuthority(this Microsoft.Extensions.Http.Resilience.IStandardHedgingHandlerBuilder builder, Microsoft.Extensions.Compliance.Classification.DataClassification classification);", + "Member": "static Microsoft.Extensions.Http.Resilience.IStandardHedgingHandlerBuilder Microsoft.Extensions.Http.Resilience.StandardHedgingHandlerBuilderExtensions.SelectPipelineByAuthority(this Microsoft.Extensions.Http.Resilience.IStandardHedgingHandlerBuilder builder, Microsoft.Extensions.Compliance.Classification.DataClassification classification);", "Stage": "Stable" } ] @@ -604,4 +604,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpRetryStrategyOptions.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpRetryStrategyOptions.cs index 89b509ff749..7d889b606cd 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpRetryStrategyOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpRetryStrategyOptions.cs @@ -5,6 +5,7 @@ using System.Net.Http; using System.Threading.Tasks; using Microsoft.Extensions.Http.Resilience.Internal; +using Polly; using Polly.Retry; namespace Microsoft.Extensions.Http.Resilience; @@ -26,7 +27,7 @@ public class HttpRetryStrategyOptions : RetryStrategyOptions new ValueTask(HttpClientResiliencePredicates.IsTransientHttpOutcome(args.Outcome)); - BackoffType = RetryBackoffType.Exponential; + BackoffType = DelayBackoffType.Exponential; ShouldRetryAfterHeader = true; UseJitter = true; } @@ -39,7 +40,7 @@ public HttpRetryStrategyOptions() /// /// /// If the property is set to then the generator will resolve the delay - /// based on the Retry-After header rules, otherwise it will return + /// based on the Retry-After header rules, otherwise it will return /// that was suggested by the retry strategy. /// public bool ShouldRetryAfterHeader @@ -51,15 +52,15 @@ public bool ShouldRetryAfterHeader if (_shouldRetryAfterHeader) { - RetryDelayGenerator = args => args.Outcome.Result switch + DelayGenerator = args => args.Outcome.Result switch { HttpResponseMessage response when RetryAfterHelper.TryParse(response, TimeProvider.System, out var retryAfter) => new ValueTask(retryAfter), - _ => new ValueTask(args.Arguments.DelayHint) + _ => new ValueTask(args.DelayHint) }; } else { - RetryDelayGenerator = null; + DelayGenerator = null; } } } diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpClientBuilderExtensions.Resilience.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpClientBuilderExtensions.Resilience.cs index 4fe7c982f8d..5845cefe89d 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpClientBuilderExtensions.Resilience.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpClientBuilderExtensions.Resilience.cs @@ -21,88 +21,88 @@ namespace Microsoft.Extensions.Http.Resilience; public static partial class HttpClientBuilderExtensions { /// - /// Adds a resilience strategy handler that uses a named inline resilience strategy. + /// Adds a resilience pipeline handler that uses a named inline resilience pipeline. /// /// The builder instance. - /// The custom identifier for the resilience strategy, used in the name of the strategy. - /// The callback that configures the strategy. - /// The HTTP strategy builder instance. + /// The custom identifier for the resilience pipeline, used in the name of the pipeline. + /// The callback that configures the pipeline. + /// The HTTP pipeline builder instance. /// - /// The final strategy name is combination of and . - /// Use strategy name identifier if your HTTP client contains multiple resilience handlers. + /// The final pipeline name is combination of and . + /// Use pipeline name identifier if your HTTP client contains multiple resilience handlers. /// - public static IHttpResilienceStrategyBuilder AddResilienceHandler( + public static IHttpResiliencePipelineBuilder AddResilienceHandler( this IHttpClientBuilder builder, - string strategyName, - Action> configure) + string pipelineName, + Action> configure) { _ = Throw.IfNull(builder); - _ = Throw.IfNullOrEmpty(strategyName); + _ = Throw.IfNullOrEmpty(pipelineName); _ = Throw.IfNull(configure); - return builder.AddResilienceHandler(strategyName, ConfigureBuilder); + return builder.AddResilienceHandler(pipelineName, ConfigureBuilder); - void ConfigureBuilder(CompositeStrategyBuilder builder, ResilienceHandlerContext context) => configure(builder); + void ConfigureBuilder(ResiliencePipelineBuilder builder, ResilienceHandlerContext context) => configure(builder); } /// - /// Adds a resilience strategy handler that uses a named inline resilience strategy. + /// Adds a resilience pipeline handler that uses a named inline resilience pipeline. /// /// The builder instance. - /// The custom identifier for the resilience strategy, used in the name of the strategy. - /// The callback that configures the strategy. - /// The HTTP strategy builder instance. + /// The custom identifier for the resilience pipeline, used in the name of the pipeline. + /// The callback that configures the pipeline. + /// The HTTP pipeline builder instance. /// - /// The final strategy name is combination of and . - /// Use strategy name identifier if your HTTP client contains multiple resilience handlers. + /// The final pipeline name is combination of and . + /// Use pipeline name identifier if your HTTP client contains multiple resilience handlers. /// - public static IHttpResilienceStrategyBuilder AddResilienceHandler( + public static IHttpResiliencePipelineBuilder AddResilienceHandler( this IHttpClientBuilder builder, - string strategyName, - Action, ResilienceHandlerContext> configure) + string pipelineName, + Action, ResilienceHandlerContext> configure) { _ = Throw.IfNull(builder); - _ = Throw.IfNullOrEmpty(strategyName); + _ = Throw.IfNullOrEmpty(pipelineName); _ = Throw.IfNull(configure); - var strategyBuilder = builder.AddHttpResilienceStrategy(strategyName, configure); + var pipelineBuilder = builder.AddHttpResiliencePipeline(pipelineName, configure); _ = builder.AddHttpMessageHandler(serviceProvider => { - var selector = CreateStrategySelector(serviceProvider, strategyBuilder.StrategyName); - var provider = serviceProvider.GetRequiredService>(); + var selector = CreatePipelineSelector(serviceProvider, pipelineBuilder.PipelineName); + var provider = serviceProvider.GetRequiredService>(); return new ResilienceHandler(selector); }); - return strategyBuilder; + return pipelineBuilder; } - private static Func> CreateStrategySelector(IServiceProvider serviceProvider, string strategyName) + private static Func> CreatePipelineSelector(IServiceProvider serviceProvider, string pipelineName) { - var resilienceProvider = serviceProvider.GetRequiredService>(); - var strategyKeyProvider = serviceProvider.GetStrategyKeyProvider(strategyName); + var resilienceProvider = serviceProvider.GetRequiredService>(); + var pipelineKeyProvider = serviceProvider.GetPipelineKeyProvider(pipelineName); - if (strategyKeyProvider == null) + if (pipelineKeyProvider == null) { - var strategy = resilienceProvider.GetStrategy(new HttpKey(strategyName, string.Empty)); - return _ => strategy; + var pipeline = resilienceProvider.GetPipeline(new HttpKey(pipelineName, string.Empty)); + return _ => pipeline; } else { - TouchStrategyKey(strategyKeyProvider); + TouchPipelineKey(pipelineKeyProvider); return request => { - var key = strategyKeyProvider(request); - return resilienceProvider.GetStrategy(new HttpKey(strategyName, key)); + var key = pipelineKeyProvider(request); + return resilienceProvider.GetPipeline(new HttpKey(pipelineName, key)); }; } } - private static void TouchStrategyKey(Func provider) + private static void TouchPipelineKey(Func provider) { - // this piece of code eagerly checks that the strategy key provider is correctly configured + // this piece of code eagerly checks that the pipeline key provider is correctly configured // combined with HttpClient auto-activation we can detect any issues on startup #pragma warning disable S1075 // URIs should not be hardcoded - this URL is not used for any real request, nor in any telemetry using var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:123"); @@ -110,19 +110,19 @@ private static void TouchStrategyKey(Func provider) _ = provider(request); } - private static HttpResilienceStrategyBuilder AddHttpResilienceStrategy( + private static HttpResiliencePipelineBuilder AddHttpResiliencePipeline( this IHttpClientBuilder builder, string name, - Action, ResilienceHandlerContext> configure) + Action, ResilienceHandlerContext> configure) { - var strategyName = StrategyNameHelper.GetName(builder.Name, name); - var key = new HttpKey(strategyName, string.Empty); + var pipelineName = PipelineNameHelper.GetName(builder.Name, name); + var key = new HttpKey(pipelineName, string.Empty); - _ = builder.Services.AddResilienceStrategy(key, (builder, context) => configure(builder, new ResilienceHandlerContext(context))); + _ = builder.Services.AddResiliencePipeline(key, (builder, context) => configure(builder, new ResilienceHandlerContext(context))); ConfigureHttpServices(builder.Services); - return new(strategyName, builder.Services); + return new(pipelineName, builder.Services); } private static void ConfigureHttpServices(IServiceCollection services) @@ -136,7 +136,7 @@ private static void ConfigureHttpServices(IServiceCollection services) services.Add(Marker.ServiceDescriptor); // This code configure the multi-instance support of the registry - _ = services.Configure>(options => + _ = services.Configure>(options => { options.BuilderNameFormatter = key => key.Name; options.InstanceNameFormatter = key => key.InstanceName; @@ -163,5 +163,5 @@ private sealed class Marker public static readonly ServiceDescriptor ServiceDescriptor = ServiceDescriptor.Singleton(); } - private record HttpResilienceStrategyBuilder(string StrategyName, IServiceCollection Services) : IHttpResilienceStrategyBuilder; + private record HttpResiliencePipelineBuilder(string PipelineName, IServiceCollection Services) : IHttpResiliencePipelineBuilder; } diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpClientBuilderExtensions.StandardResilience.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpClientBuilderExtensions.StandardResilience.cs index d85902bd188..045cd9eae86 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpClientBuilderExtensions.StandardResilience.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpClientBuilderExtensions.StandardResilience.cs @@ -23,10 +23,10 @@ public static partial class HttpClientBuilderExtensions /// The section that the options will bind against. /// The HTTP resilience handler builder instance. /// - /// The resilience strategy combines multiple strategies that are configured based on HTTP-specific options with recommended defaults. + /// The resilience pipeline combines multiple strategies that are configured based on HTTP-specific options with recommended defaults. /// See for more details about the individual resilience strategies configured by this method. /// - public static IHttpStandardResilienceStrategyBuilder AddStandardResilienceHandler(this IHttpClientBuilder builder, IConfigurationSection section) + public static IHttpStandardResiliencePipelineBuilder AddStandardResilienceHandler(this IHttpClientBuilder builder, IConfigurationSection section) { _ = Throw.IfNull(builder); _ = Throw.IfNull(section); @@ -41,10 +41,10 @@ public static IHttpStandardResilienceStrategyBuilder AddStandardResilienceHandle /// The callback that configures the options. /// The HTTP resilience handler builder instance. /// - /// The resilience strategy combines multiple strategies that are configured based on HTTP-specific options with recommended defaults. + /// The resilience pipeline combines multiple strategies that are configured based on HTTP-specific options with recommended defaults. /// See for more details about the individual resilience strategies configured by this method. /// - public static IHttpStandardResilienceStrategyBuilder AddStandardResilienceHandler(this IHttpClientBuilder builder, Action configure) + public static IHttpStandardResiliencePipelineBuilder AddStandardResilienceHandler(this IHttpClientBuilder builder, Action configure) { _ = Throw.IfNull(builder); _ = Throw.IfNull(configure); @@ -58,14 +58,14 @@ public static IHttpStandardResilienceStrategyBuilder AddStandardResilienceHandle /// The builder instance. /// The HTTP resilience handler builder instance. /// - /// The resilience strategy combines multiple strategies that are configured based on HTTP-specific options with recommended defaults. + /// The resilience pipeline combines multiple strategies that are configured based on HTTP-specific options with recommended defaults. /// See for more details about the individual resilience strategies configured by this method. /// - public static IHttpStandardResilienceStrategyBuilder AddStandardResilienceHandler(this IHttpClientBuilder builder) + public static IHttpStandardResiliencePipelineBuilder AddStandardResilienceHandler(this IHttpClientBuilder builder) { _ = Throw.IfNull(builder); - var optionsName = StrategyNameHelper.GetName(builder.Name, StandardIdentifier); + var optionsName = PipelineNameHelper.GetName(builder.Name, StandardIdentifier); _ = builder.Services.AddOptionsWithValidateOnStart(optionsName); _ = builder.Services.AddOptionsWithValidateOnStart(optionsName); @@ -85,8 +85,8 @@ public static IHttpStandardResilienceStrategyBuilder AddStandardResilienceHandle .AddTimeout(options.AttemptTimeoutOptions); }); - return new HttpStandardResilienceStrategyBuilder(optionsName, builder.Services); + return new HttpStandardResiliencePipelineBuilder(optionsName, builder.Services); } - private record HttpStandardResilienceStrategyBuilder(string StrategyName, IServiceCollection Services) : IHttpStandardResilienceStrategyBuilder; + private record HttpStandardResiliencePipelineBuilder(string PipelineName, IServiceCollection Services) : IHttpStandardResiliencePipelineBuilder; } diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpResilienceStrategyBuilderExtensions.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpResiliencePipelineBuilderExtensions.cs similarity index 64% rename from src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpResilienceStrategyBuilderExtensions.cs rename to src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpResiliencePipelineBuilderExtensions.cs index bde9b4212e9..7a04a0e5b6d 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpResilienceStrategyBuilderExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpResiliencePipelineBuilderExtensions.cs @@ -11,39 +11,39 @@ namespace Microsoft.Extensions.Http.Resilience; /// -/// Extensions for . +/// Extensions for . /// -public static class HttpResilienceStrategyBuilderExtensions +public static class HttpResiliencePipelineBuilderExtensions { /// - /// Instructs the underlying builder to select the strategy instance by redacted authority (scheme + host + port). + /// Instructs the underlying builder to select the pipeline instance by redacted authority (scheme + host + port). /// /// The builder instance. /// The data class associated with the authority. /// The same builder instance. /// The authority is redacted using retrieved for . - public static IHttpResilienceStrategyBuilder SelectStrategyByAuthority(this IHttpResilienceStrategyBuilder builder, DataClassification classification) + public static IHttpResiliencePipelineBuilder SelectPipelineByAuthority(this IHttpResiliencePipelineBuilder builder, DataClassification classification) { _ = Throw.IfNull(builder); - StrategyKeyProviderHelper.SelectStrategyByAuthority(builder.Services, builder.StrategyName, classification); + PipelineKeyProviderHelper.SelectPipelineByAuthority(builder.Services, builder.PipelineName, classification); return builder; } /// - /// Instructs the underlying builder to select the strategy instance by custom selector. + /// Instructs the underlying builder to select the pipeline instance by custom selector. /// /// The builder instance. /// The factory that returns a key selector. /// The same builder instance. - /// The strategy key is used in metrics and logs, so don't return any sensitive values. - public static IHttpResilienceStrategyBuilder SelectStrategyBy(this IHttpResilienceStrategyBuilder builder, Func> selectorFactory) + /// The pipeline key is used in metrics and logs, so don't return any sensitive values. + public static IHttpResiliencePipelineBuilder SelectPipelineBy(this IHttpResiliencePipelineBuilder builder, Func> selectorFactory) { _ = Throw.IfNull(builder); _ = Throw.IfNull(selectorFactory); - StrategyKeyProviderHelper.SelectStrategyBy(builder.Services, builder.StrategyName, selectorFactory); + PipelineKeyProviderHelper.SelectPipelineBy(builder.Services, builder.PipelineName, selectorFactory); return builder; } diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpStandardResilienceBuilderBuilderExtensions.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpStandardResilienceBuilderBuilderExtensions.cs index e1605362d1b..46de879876c 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpStandardResilienceBuilderBuilderExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpStandardResilienceBuilderBuilderExtensions.cs @@ -15,25 +15,25 @@ namespace Microsoft.Extensions.Http.Resilience; /// -/// Extensions for . +/// Extensions for . /// public static class HttpStandardResilienceBuilderBuilderExtensions { /// - /// Configures the for the standard resilience strategy. + /// Configures the for the standard resilience pipeline. /// - /// The strategy builder. + /// The pipeline builder. /// The section that the options will bind against. /// The same builder instance. [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(HttpStandardResilienceOptions))] - public static IHttpStandardResilienceStrategyBuilder Configure(this IHttpStandardResilienceStrategyBuilder builder, IConfigurationSection section) + public static IHttpStandardResiliencePipelineBuilder Configure(this IHttpStandardResiliencePipelineBuilder builder, IConfigurationSection section) { _ = Throw.IfNull(builder); _ = Throw.IfNull(section); var options = Throw.IfNull(section.Get()); _ = builder.Services.Configure( - builder.StrategyName, + builder.PipelineName, section, o => o.ErrorOnUnknownConfiguration = true); @@ -41,13 +41,13 @@ public static IHttpStandardResilienceStrategyBuilder Configure(this IHttpStandar } /// - /// Configures the for the standard resilience strategy. + /// Configures the for the standard resilience pipeline. /// - /// The strategy builder. + /// The pipeline builder. /// The configure method. /// The same builder instance. #pragma warning disable S3872 // Parameter names should not duplicate the names of their methods - public static IHttpStandardResilienceStrategyBuilder Configure(this IHttpStandardResilienceStrategyBuilder builder, Action configure) + public static IHttpStandardResiliencePipelineBuilder Configure(this IHttpStandardResiliencePipelineBuilder builder, Action configure) #pragma warning restore S3872 // Parameter names should not duplicate the names of their methods { _ = Throw.IfNull(builder); @@ -57,53 +57,53 @@ public static IHttpStandardResilienceStrategyBuilder Configure(this IHttpStandar } /// - /// Configures the for the standard resilience strategy. + /// Configures the for the standard resilience pipeline. /// - /// The strategy builder. + /// The pipeline builder. /// The configure method. /// The same builder instance. #pragma warning disable S3872 // Parameter names should not duplicate the names of their methods [Experimental(diagnosticId: Experiments.Resilience, UrlFormat = Experiments.UrlFormat)] - public static IHttpStandardResilienceStrategyBuilder Configure(this IHttpStandardResilienceStrategyBuilder builder, Action configure) + public static IHttpStandardResiliencePipelineBuilder Configure(this IHttpStandardResiliencePipelineBuilder builder, Action configure) #pragma warning restore S3872 // Parameter names should not duplicate the names of their methods { _ = Throw.IfNull(builder); _ = Throw.IfNull(configure); - _ = builder.Services.AddOptions(builder.StrategyName).Configure(configure); + _ = builder.Services.AddOptions(builder.PipelineName).Configure(configure); return builder; } /// - /// Instructs the underlying builder to select the strategy instance by redacted authority (scheme + host + port). + /// Instructs the underlying builder to select the pipeline instance by redacted authority (scheme + host + port). /// - /// The strategy builder. + /// The pipeline builder. /// The data class associated with the authority. /// The same builder instance. /// The authority is redacted using retrieved for . - public static IHttpStandardResilienceStrategyBuilder SelectStrategyByAuthority(this IHttpStandardResilienceStrategyBuilder builder, DataClassification classification) + public static IHttpStandardResiliencePipelineBuilder SelectPipelineByAuthority(this IHttpStandardResiliencePipelineBuilder builder, DataClassification classification) { _ = Throw.IfNull(builder); - StrategyKeyProviderHelper.SelectStrategyByAuthority(builder.Services, builder.StrategyName, classification); + PipelineKeyProviderHelper.SelectPipelineByAuthority(builder.Services, builder.PipelineName, classification); return builder; } /// - /// Instructs the underlying builder to select the strategy instance by custom selector. + /// Instructs the underlying builder to select the pipeline instance by custom selector. /// - /// The strategy builder. + /// The pipeline builder. /// The factory that returns a key selector. /// The same builder instance. /// The pipeline key is used in metrics and logs, do not return any sensitive value. - public static IHttpStandardResilienceStrategyBuilder SelectStrategyBy(this IHttpStandardResilienceStrategyBuilder builder, Func> selectorFactory) + public static IHttpStandardResiliencePipelineBuilder SelectPipelineBy(this IHttpStandardResiliencePipelineBuilder builder, Func> selectorFactory) { _ = Throw.IfNull(builder); _ = Throw.IfNull(selectorFactory); - StrategyKeyProviderHelper.SelectStrategyBy(builder.Services, builder.StrategyName, selectorFactory); + PipelineKeyProviderHelper.SelectPipelineBy(builder.Services, builder.PipelineName, selectorFactory); return builder; } diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpStandardResilienceOptions.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpStandardResilienceOptions.cs index 82ad3aea43b..539b3ad6ddf 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpStandardResilienceOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/HttpStandardResilienceOptions.cs @@ -17,14 +17,14 @@ namespace Microsoft.Extensions.Http.Resilience; /// /// Bulkhead -> Total Request Timeout -> Retry -> Circuit Breaker -> Attempt Timeout. /// -/// The configuration of each strategy is initialized with the default options per type. The request goes through these strategies: +/// The configuration of each pipeline is initialized with the default options per type. The request goes through these strategies: /// -/// Total request timeout strategy applies an overall timeout to the execution, +/// Total request timeout pipeline applies an overall timeout to the execution, /// ensuring that the request including hedging attempts, does not exceed the configured limit. -/// The retry strategy retries the request in case the dependency is slow or returns a transient error. -/// The bulkhead strategy limits the maximum number of concurrent requests being send to the dependency. +/// The retry pipeline retries the request in case the dependency is slow or returns a transient error. +/// The bulkhead pipeline limits the maximum number of concurrent requests being send to the dependency. /// The circuit breaker blocks the execution if too many direct failures or timeouts are detected. -/// The attempt timeout strategy limits each request attempt duration and throws if its exceeded. +/// The attempt timeout pipeline limits each request attempt duration and throws if its exceeded. /// /// public class HttpStandardResilienceOptions @@ -39,7 +39,7 @@ public class HttpStandardResilienceOptions [ValidateObjectMembers] public HttpRateLimiterStrategyOptions RateLimiterOptions { get; set; } = new HttpRateLimiterStrategyOptions { - Name = StandardStrategyNames.RateLimiter + Name = StandardPipelineNames.RateLimiter }; /// @@ -52,11 +52,11 @@ public class HttpStandardResilienceOptions [ValidateObjectMembers] public HttpTimeoutStrategyOptions TotalRequestTimeoutOptions { get; set; } = new HttpTimeoutStrategyOptions { - Name = StandardStrategyNames.TotalRequestTimeout + Name = StandardPipelineNames.TotalRequestTimeout }; /// - /// Gets or sets the retry strategy options. + /// Gets or sets the retry pipeline options. /// /// /// By default, this property is initialized with a unique instance of using default properties values. @@ -65,7 +65,7 @@ public class HttpStandardResilienceOptions [ValidateObjectMembers] public HttpRetryStrategyOptions RetryOptions { get; set; } = new HttpRetryStrategyOptions { - Name = StandardStrategyNames.Retry + Name = StandardPipelineNames.Retry }; /// @@ -78,11 +78,11 @@ public class HttpStandardResilienceOptions [ValidateObjectMembers] public HttpCircuitBreakerStrategyOptions CircuitBreakerOptions { get; set; } = new HttpCircuitBreakerStrategyOptions { - Name = StandardStrategyNames.CircuitBreaker + Name = StandardPipelineNames.CircuitBreaker }; /// - /// Gets or sets the options for the timeout strategy applied per each request attempt. + /// Gets or sets the options for the timeout pipeline applied per each request attempt. /// /// /// By default, this property is initialized with a unique instance of @@ -93,6 +93,6 @@ public class HttpStandardResilienceOptions public HttpTimeoutStrategyOptions AttemptTimeoutOptions { get; set; } = new() { Timeout = TimeSpan.FromSeconds(10), - Name = StandardStrategyNames.AttemptTimeout + Name = StandardPipelineNames.AttemptTimeout }; } diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/IHttpResilienceStrategyBuilder.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/IHttpResiliencePipelineBuilder.cs similarity index 67% rename from src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/IHttpResilienceStrategyBuilder.cs rename to src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/IHttpResiliencePipelineBuilder.cs index 3684400b0ba..1727980d1f3 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/IHttpResilienceStrategyBuilder.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/IHttpResiliencePipelineBuilder.cs @@ -6,14 +6,14 @@ namespace Microsoft.Extensions.Http.Resilience; /// -/// The builder for configuring the HTTP client resilience strategy. +/// The builder for configuring the HTTP client resilience pipeline. /// -public interface IHttpResilienceStrategyBuilder +public interface IHttpResiliencePipelineBuilder { /// - /// Gets the name of the resilience strategy configured by this builder. + /// Gets the name of the resilience pipeline configured by this builder. /// - string StrategyName { get; } + string PipelineName { get; } /// /// Gets the application service collection. diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/IHttpStandardResilienceStrategyBuilder.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/IHttpStandardResiliencePipelineBuilder.cs similarity index 67% rename from src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/IHttpStandardResilienceStrategyBuilder.cs rename to src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/IHttpStandardResiliencePipelineBuilder.cs index 3a924f9d620..f9cf0c3b738 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/IHttpStandardResilienceStrategyBuilder.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/IHttpStandardResiliencePipelineBuilder.cs @@ -6,14 +6,14 @@ namespace Microsoft.Extensions.Http.Resilience; /// -/// The builder for the standard HTTP resilience strategy. +/// The builder for the standard HTTP resilience pipeline. /// -public interface IHttpStandardResilienceStrategyBuilder +public interface IHttpStandardResiliencePipelineBuilder { /// - /// Gets the name of the resilience strategy configured by this builder. + /// Gets the name of the resilience pipeline configured by this builder. /// - string StrategyName { get; } + string PipelineName { get; } /// /// Gets the application service collection. diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ByAuthorityStrategyKeyProvider.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ByAuthorityPipelineKeyProvider.cs similarity index 63% rename from src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ByAuthorityStrategyKeyProvider.cs rename to src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ByAuthorityPipelineKeyProvider.cs index 5b9e59c5c7f..7d030829f9a 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ByAuthorityStrategyKeyProvider.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ByAuthorityPipelineKeyProvider.cs @@ -9,40 +9,40 @@ namespace Microsoft.Extensions.Http.Resilience.Internal; -internal sealed class ByAuthorityStrategyKeyProvider +internal sealed class ByAuthorityPipelineKeyProvider { private readonly Redactor _redactor; private readonly ConcurrentDictionary<(string scheme, string host, int port), string> _cache = new(); - public ByAuthorityStrategyKeyProvider(Redactor redactor) + public ByAuthorityPipelineKeyProvider(Redactor redactor) { _redactor = redactor; } - public string GetStrategyKey(HttpRequestMessage requestMessage) + public string GetPipelineKey(HttpRequestMessage requestMessage) { var url = requestMessage.RequestUri ?? throw new InvalidOperationException("The request message must have a URL specified."); var key = (url.Scheme, url.Host, url.Port); // We could use GetOrAdd for simplification but that would force us to allocate the lambda for every call. - if (_cache.TryGetValue(key, out var strategyKey)) + if (_cache.TryGetValue(key, out var pipelineKey)) { - return strategyKey; + return pipelineKey; } - strategyKey = url.GetLeftPart(UriPartial.Authority); - strategyKey = _redactor.Redact(strategyKey); + pipelineKey = url.GetLeftPart(UriPartial.Authority); + pipelineKey = _redactor.Redact(pipelineKey); - if (string.IsNullOrEmpty(strategyKey)) + if (string.IsNullOrEmpty(pipelineKey)) { Throw.InvalidOperationException( - "The redacted strategy key is an empty string and cannot be used for the strategy selection. Is redaction correctly configured?"); + "The redacted pipeline key is an empty string and cannot be used for the pipeline selection. Is redaction correctly configured?"); } // sometimes this can be called twice (multiple concurrent requests), but we don't care - _cache[key] = strategyKey!; + _cache[key] = pipelineKey!; - return strategyKey!; + return pipelineKey!; } } diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/StrategyKeyOptions.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/PipelineKeyOptions.cs similarity index 76% rename from src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/StrategyKeyOptions.cs rename to src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/PipelineKeyOptions.cs index cc10294aaa6..66265215a92 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/StrategyKeyOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/PipelineKeyOptions.cs @@ -7,9 +7,9 @@ namespace Microsoft.Extensions.Http.Resilience.Internal; /// -/// The provider that returns the strategy key from the request message. +/// The provider that returns the pipeline key from the request message. /// -internal sealed class StrategyKeyOptions +internal sealed class PipelineKeyOptions { public Func? KeyProvider { get; set; } } diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/PipelineKeyProviderHelper.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/PipelineKeyProviderHelper.cs new file mode 100644 index 00000000000..f755a6fe1ae --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/PipelineKeyProviderHelper.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Net.Http; +using Microsoft.Extensions.Compliance.Classification; +using Microsoft.Extensions.Compliance.Redaction; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.Http.Resilience.Internal; + +internal static class PipelineKeyProviderHelper +{ + public static void SelectPipelineByAuthority(IServiceCollection services, string pipelineName, DataClassification classification) + { + UsePipelineKeyProvider(services, pipelineName, serviceProvider => + { + var redactor = serviceProvider.GetRequiredService().GetRedactor(classification); + + return new ByAuthorityPipelineKeyProvider(redactor).GetPipelineKey; + }); + } + + public static void SelectPipelineBy(IServiceCollection services, string pipelineName, Func> selectorFactory) + { + UsePipelineKeyProvider(services, pipelineName, serviceProvider => selectorFactory(serviceProvider)); + } + + public static Func? GetPipelineKeyProvider(this IServiceProvider provider, string pipelineName) + { + return provider.GetRequiredService>().Get(pipelineName).KeyProvider; + } + + private static void UsePipelineKeyProvider(IServiceCollection services, string pipelineName, Func> factory) + { + _ = services.AddOptions(pipelineName).Configure((options, provider) => options.KeyProvider = factory(provider)); + } +} diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/StrategyNameHelper.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/PipelineNameHelper.cs similarity index 54% rename from src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/StrategyNameHelper.cs rename to src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/PipelineNameHelper.cs index d7592f834d5..fd6cf793b40 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/StrategyNameHelper.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/PipelineNameHelper.cs @@ -3,7 +3,7 @@ namespace Microsoft.Extensions.Http.Resilience.Internal; -internal static class StrategyNameHelper +internal static class PipelineNameHelper { - public static string GetName(string httpClientName, string strategyIdentifier) => $"{httpClientName}-{strategyIdentifier}"; + public static string GetName(string httpClientName, string pipelineIdentifier) => $"{httpClientName}-{pipelineIdentifier}"; } diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ResilienceHandler.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ResilienceHandler.cs index 871fae33022..050b64fe7a5 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ResilienceHandler.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ResilienceHandler.cs @@ -16,17 +16,17 @@ namespace Microsoft.Extensions.Http.Resilience.Internal; /// internal sealed class ResilienceHandler : DelegatingHandler { - private readonly Func> _strategyProvider; + private readonly Func> _pipelineProvider; - public ResilienceHandler(Func> strategyProvider) + public ResilienceHandler(Func> pipelineProvider) { - _strategyProvider = strategyProvider; + _pipelineProvider = pipelineProvider; } /// protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { - var strategy = _strategyProvider(request); + var pipeline = _pipelineProvider(request); var created = false; if (request.GetResilienceContext() is not ResilienceContext context) { @@ -44,7 +44,7 @@ protected override async Task SendAsync(HttpRequestMessage try { - var outcome = await strategy.ExecuteOutcomeAsync( + var outcome = await pipeline.ExecuteOutcomeAsync( static async (context, state) => { var request = context.Properties.GetValue(ResilienceKeys.RequestMessage, state.request); diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/StandardStrategyNames.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/StandardPipelineNames.cs similarity index 92% rename from src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/StandardStrategyNames.cs rename to src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/StandardPipelineNames.cs index 21e6949398f..8eeacb7a247 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/StandardStrategyNames.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/StandardPipelineNames.cs @@ -3,7 +3,7 @@ namespace Microsoft.Extensions.Http.Resilience.Internal; -internal static class StandardStrategyNames +internal static class StandardPipelineNames { public const string CircuitBreaker = "Standard-CircuitBreaker"; diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/StrategyKeyProviderHelper.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/StrategyKeyProviderHelper.cs deleted file mode 100644 index bcf8e58e0f7..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/StrategyKeyProviderHelper.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Net.Http; -using Microsoft.Extensions.Compliance.Classification; -using Microsoft.Extensions.Compliance.Redaction; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace Microsoft.Extensions.Http.Resilience.Internal; - -internal static class StrategyKeyProviderHelper -{ - public static void SelectStrategyByAuthority(IServiceCollection services, string strategyName, DataClassification classification) - { - UseStrategyKeyProvider(services, strategyName, serviceProvider => - { - var redactor = serviceProvider.GetRequiredService().GetRedactor(classification); - - return new ByAuthorityStrategyKeyProvider(redactor).GetStrategyKey; - }); - } - - public static void SelectStrategyBy(IServiceCollection services, string strategyName, Func> selectorFactory) - { - UseStrategyKeyProvider(services, strategyName, serviceProvider => selectorFactory(serviceProvider)); - } - - public static Func? GetStrategyKeyProvider(this IServiceProvider provider, string strategyName) - { - return provider.GetRequiredService>().Get(strategyName).KeyProvider; - } - - private static void UseStrategyKeyProvider(IServiceCollection services, string strategyName, Func> factory) - { - _ = services.AddOptions(strategyName).Configure((options, provider) => options.KeyProvider = factory(provider)); - } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/Validators/HttpStandardResilienceOptionsCustomValidator.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/Validators/HttpStandardResilienceOptionsCustomValidator.cs index c5ef4af3cef..f28e463404e 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/Validators/HttpStandardResilienceOptionsCustomValidator.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/Validators/HttpStandardResilienceOptionsCustomValidator.cs @@ -30,7 +30,7 @@ public ValidateOptionsResult Validate(string? name, HttpStandardResilienceOption $"Attempt Timeout: {options.AttemptTimeoutOptions.Timeout.TotalSeconds}s"); } - if (options.RetryOptions.RetryCount > 0) + if (options.RetryOptions.MaxRetryAttempts > 0) { TimeSpan retrySum = ValidationHelper.GetAggregatedDelay(options.RetryOptions); diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/ResilienceHandlerContext.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/ResilienceHandlerContext.cs index 06cb29ecd86..07bb22e23da 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/ResilienceHandlerContext.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/ResilienceHandlerContext.cs @@ -3,18 +3,18 @@ using System; using Microsoft.Extensions.Http.Resilience.Internal; -using Polly.Extensions.DependencyInjection; +using Polly.DependencyInjection; namespace Microsoft.Extensions.Http.Resilience; /// -/// The context used when building a resilience strategy HTTP handler. +/// The context used when building a resilience pipeline HTTP handler. /// public sealed class ResilienceHandlerContext { - private readonly AddResilienceStrategyContext _context; + private readonly AddResiliencePipelineContext _context; - internal ResilienceHandlerContext(AddResilienceStrategyContext context) + internal ResilienceHandlerContext(AddResiliencePipelineContext context) { _context = context; } @@ -27,15 +27,15 @@ internal ResilienceHandlerContext(AddResilienceStrategyContext context) /// /// Gets the name of the builder being built. /// - public string BuilderName => _context.StrategyKey.Name; + public string BuilderName => _context.PipelineKey.Name; /// - /// Gets the instance name of resilience strategy being built. + /// Gets the instance name of resilience pipeline being built. /// - public string InstanceName => _context.StrategyKey.InstanceName; + public string InstanceName => _context.PipelineKey.InstanceName; /// - /// Enables dynamic reloading of the resilience strategy whenever the options are changed. + /// Enables dynamic reloading of the resilience pipeline whenever the options are changed. /// /// The options type to listen to. /// The named options, if any. diff --git a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/FailureEventMetricsOptions.cs b/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/FailureEventMetricsOptions.cs index 4471efa09e0..720018215f4 100644 --- a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/FailureEventMetricsOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/FailureEventMetricsOptions.cs @@ -9,4 +9,9 @@ namespace Microsoft.Extensions.Resilience.Internal; internal sealed class FailureEventMetricsOptions { public Dictionary> Factories { get; } = new(); + + public void ConfigureFailureResultContext(Func configure) + { + Factories[typeof(TResult)] = value => configure((TResult)value); + } } diff --git a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/ResilienceEnricher.cs b/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/ResilienceMeteringEnricher.cs similarity index 68% rename from src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/ResilienceEnricher.cs rename to src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/ResilienceMeteringEnricher.cs index 8fac8f3a174..28876db3d51 100644 --- a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/ResilienceEnricher.cs +++ b/src/Libraries/Microsoft.Extensions.Resilience/Resilience/Internal/ResilienceMeteringEnricher.cs @@ -8,17 +8,17 @@ using Microsoft.Extensions.Diagnostics.ExceptionSummarization; using Microsoft.Extensions.Http.Telemetry; using Microsoft.Extensions.Options; -using Polly.Extensions.Telemetry; +using Polly.Telemetry; namespace Microsoft.Extensions.Resilience.Internal; -internal sealed class ResilienceEnricher +internal sealed class ResilienceMeteringEnricher : MeteringEnricher { private readonly FrozenDictionary> _faultFactories; private readonly IOutgoingRequestContext? _outgoingRequestContext; private readonly IExceptionSummarizer _exceptionSummarizer; - public ResilienceEnricher( + public ResilienceMeteringEnricher( IOptions metricsOptions, IEnumerable outgoingRequestContext, IExceptionSummarizer exceptionSummarizer) @@ -28,38 +28,28 @@ public ResilienceEnricher( _exceptionSummarizer = exceptionSummarizer; } - public void Enrich(EnrichmentContext context) + public override void Enrich(in EnrichmentContext context) { - if (context.Outcome?.Exception is Exception e) + var outcome = context.TelemetryEvent.Outcome; + + if (outcome?.Exception is Exception e) { context.Tags.Add(new(ResilienceTagNames.FailureSource, e.Source)); context.Tags.Add(new(ResilienceTagNames.FailureReason, e.GetType().Name)); context.Tags.Add(new(ResilienceTagNames.FailureSummary, _exceptionSummarizer.Summarize(e).ToString())); } - else if (context.Outcome?.Result is object result && _faultFactories.TryGetValue(result.GetType(), out var factory)) + else if (outcome is not null && outcome.Value.Result is object result && _faultFactories.TryGetValue(result.GetType(), out var factory)) { var failureContext = factory(result); context.Tags.Add(new(ResilienceTagNames.FailureSource, failureContext.FailureSource)); context.Tags.Add(new(ResilienceTagNames.FailureReason, failureContext.FailureReason)); context.Tags.Add(new(ResilienceTagNames.FailureSummary, failureContext.AdditionalInformation)); } - else - { - context.Tags.Add(new(ResilienceTagNames.FailureSource, null)); - context.Tags.Add(new(ResilienceTagNames.FailureReason, null)); - context.Tags.Add(new(ResilienceTagNames.FailureSummary, null)); - } - var requestMetadata = context.Context.GetRequestMetadata() ?? _outgoingRequestContext?.RequestMetadata; - if (requestMetadata is not null) + if ((context.TelemetryEvent.Context.GetRequestMetadata() ?? _outgoingRequestContext?.RequestMetadata) is RequestMetadata requestMetadata) { context.Tags.Add(new(ResilienceTagNames.RequestName, requestMetadata.RequestName)); context.Tags.Add(new(ResilienceTagNames.DependencyName, requestMetadata.DependencyName)); } - else - { - context.Tags.Add(new(ResilienceTagNames.RequestName, null)); - context.Tags.Add(new(ResilienceTagNames.DependencyName, null)); - } } } diff --git a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/ResilienceServiceCollectionExtensions.cs b/src/Libraries/Microsoft.Extensions.Resilience/Resilience/ResilienceServiceCollectionExtensions.cs index eff05c01bc9..db4965dcf51 100644 --- a/src/Libraries/Microsoft.Extensions.Resilience/Resilience/ResilienceServiceCollectionExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Resilience/Resilience/ResilienceServiceCollectionExtensions.cs @@ -11,7 +11,7 @@ using Microsoft.Extensions.Resilience.Internal; using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; -using Polly.Extensions.Telemetry; +using Polly.Telemetry; namespace Microsoft.Extensions.Resilience; @@ -46,20 +46,16 @@ public static IServiceCollection AddResilienceEnrichment(this IServiceCollection _ = Throw.IfNull(services); // let's make this call idempotent by checking if ResilienceEnricher is already added - if (services.Any(s => s.ServiceType == typeof(ResilienceEnricher))) + if (services.Any(s => s.ServiceType == typeof(ResilienceMeteringEnricher))) { return services; } - services.TryAddActivatedSingleton(); + services.TryAddActivatedSingleton(); _ = services .AddOptions() - .Configure((options, serviceProvider) => - { - var enricher = serviceProvider.GetRequiredService(); - options.Enrichers.Add(enricher.Enrich); - }); + .Configure((options, enricher) => options.MeteringEnrichers.Add(enricher)); return services; } @@ -81,9 +77,6 @@ public static IServiceCollection ConfigureFailureResultContext( _ = Throw.IfNull(services); _ = Throw.IfNull(configure); - return services.Configure(options => - { - options.Factories[typeof(TResult)] = value => configure((TResult)value); - }); + return services.Configure(options => options.ConfigureFailureResultContext(configure)); } } diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/HedgingTests.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/HedgingTests.cs index 63637eb0530..efa44613154 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/HedgingTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/HedgingTests.cs @@ -29,7 +29,7 @@ public abstract class HedgingTests : IDisposable { public const string ClientId = "clientId"; - public const int DefaultHedgingAttempts = 3; + public const int DefaultHedgingAttempts = 2; private readonly CancellationTokenSource _cancellationTokenSource; private readonly Mock _requestRoutingStrategyMock; @@ -164,7 +164,7 @@ public async Task SendAsync_NoRoutesLeftAndSomeResultPresent_ShouldReturn() using var client = CreateClientWithHandler(); var result = await client.SendAsync(request, _cancellationTokenSource.Token); - Assert.Equal(DefaultHedgingAttempts, Requests.Count); + Assert.Equal(DefaultHedgingAttempts + 1, Requests.Count); Assert.Equal(HttpStatusCode.ServiceUnavailable, result.StatusCode); } diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/HttpStandardHedgingResilienceOptionsCustomValidatorTests.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/HttpStandardHedgingResilienceOptionsCustomValidatorTests.cs index 2156818c330..e03af3edefd 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/HttpStandardHedgingResilienceOptionsCustomValidatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/HttpStandardHedgingResilienceOptionsCustomValidatorTests.cs @@ -57,12 +57,12 @@ public static IEnumerable GetOptions_ValidOptions_EnsureNoErrors_Data options = new HttpStandardHedgingResilienceOptions(); options.HedgingOptions.MaxHedgedAttempts = 1; - options.HedgingOptions.HedgingDelay = options.TotalRequestTimeoutOptions.Timeout; + options.HedgingOptions.Delay = options.TotalRequestTimeoutOptions.Timeout; yield return new object[] { options }; options = new HttpStandardHedgingResilienceOptions(); - options.HedgingOptions.HedgingDelay = TimeSpan.FromDays(1); - options.HedgingOptions.HedgingDelayGenerator = _ => new ValueTask(TimeSpan.FromDays(1)); + options.HedgingOptions.Delay = TimeSpan.FromDays(1); + options.HedgingOptions.DelayGenerator = _ => new ValueTask(TimeSpan.FromDays(1)); yield return new object[] { options }; } } @@ -89,10 +89,6 @@ public static IEnumerable GetOptions_InvalidOptions_EnsureErrors_Data options.TotalRequestTimeoutOptions.Timeout = TimeSpan.FromSeconds(2); yield return new object[] { options }; - options = new HttpStandardHedgingResilienceOptions(); - options.HedgingOptions.HedgingDelay = TimeSpan.FromDays(1); - yield return new object[] { options }; - options = new HttpStandardHedgingResilienceOptions(); options.EndpointOptions.TimeoutOptions.Timeout = options.TotalRequestTimeoutOptions.Timeout; options.EndpointOptions.CircuitBreakerOptions.SamplingDuration = TimeSpan.FromMilliseconds(options.EndpointOptions.TimeoutOptions.Timeout.TotalMilliseconds / 2); diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/StandardHedgingTests.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/StandardHedgingTests.cs index 9881dd48fb3..83cea981ff9 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/StandardHedgingTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/StandardHedgingTests.cs @@ -38,7 +38,7 @@ private static IStandardHedgingHandlerBuilder ConfigureDefaultBuilder(IHttpClien .Configure(options => { options.HedgingOptions.MaxHedgedAttempts = DefaultHedgingAttempts; - options.HedgingOptions.HedgingDelay = TimeSpan.FromMilliseconds(5); + options.HedgingOptions.Delay = TimeSpan.FromMilliseconds(5); }); } @@ -73,7 +73,7 @@ public void Configure_CallbackWithServiceProvider_Ok() { Builder.Configure((o, serviceProvider) => { - serviceProvider.GetRequiredService>().Should().NotBeNull(); + serviceProvider.GetRequiredService>().Should().NotBeNull(); o.HedgingOptions.MaxHedgedAttempts = 8; }); @@ -107,7 +107,7 @@ public void Configure_ValidConfigurationSection_ShouldInitialize() public void ActionGenerator_Ok() { var options = Builder.Services.BuildServiceProvider().GetRequiredService>().Get(Builder.Name); - var generator = options.HedgingOptions.HedgingActionGenerator; + var generator = options.HedgingOptions.ActionGenerator; var primary = ResilienceContextPool.Shared.Get(); var secondary = ResilienceContextPool.Shared.Get(); using var response = new HttpResponseMessage(HttpStatusCode.OK); @@ -167,21 +167,19 @@ public void Configure_EmptyConfigurationSection_ShouldThrow() public void VerifyPipeline() { var serviceProvider = Builder.Services.BuildServiceProvider(); - var strategyProvider = serviceProvider.GetRequiredService>(); + var pipelineProvider = serviceProvider.GetRequiredService>(); // primary handler - var primary = strategyProvider.GetStrategy(new HttpKey("clientId-standard-hedging", "instance")).GetInnerStrategies(); - primary.HasTelemetry.Should().BeTrue(); + var primary = pipelineProvider.GetPipeline(new HttpKey("clientId-standard-hedging", "instance")).GetPipelineDescriptor(); primary.IsReloadable.Should().BeTrue(); primary.Strategies.Should().HaveCount(4); - primary.Strategies[0].StrategyType.Should().Be(typeof(RoutingResilienceStrategy)); - primary.Strategies[1].StrategyType.Should().Be(typeof(RequestMessageSnapshotStrategy)); + primary.Strategies[0].StrategyInstance.Should().BeOfType(); + primary.Strategies[1].StrategyInstance.Should().BeOfType(); primary.Strategies[2].Options.Should().BeOfType(); primary.Strategies[3].Options.Should().BeOfType(); // inner handler - var inner = strategyProvider.GetStrategy(new HttpKey("clientId-standard-hedging-endpoint", "instance")).GetInnerStrategies(); - inner.HasTelemetry.Should().BeTrue(); + var inner = pipelineProvider.GetPipeline(new HttpKey("clientId-standard-hedging-endpoint", "instance")).GetPipelineDescriptor(); inner.IsReloadable.Should().BeTrue(); inner.Strategies.Should().HaveCount(3); inner.Strategies[0].Options.Should().BeOfType(); @@ -194,21 +192,21 @@ public void VerifyPipeline() [Theory] public async Task VerifyPipelineSelection(string? customKey) { - var noPolicy = NullResilienceStrategy.Instance; - var provider = new Mock>(MockBehavior.Strict); + var noPolicy = ResiliencePipeline.Empty; + var provider = new Mock>(MockBehavior.Strict); Builder.Services.AddSingleton(provider.Object); if (customKey == null) { - Builder.SelectStrategyByAuthority(SimpleClassifications.PublicData); + Builder.SelectPipelineByAuthority(SimpleClassifications.PublicData); } else { - Builder.SelectStrategyBy(_ => _ => customKey); + Builder.SelectPipelineBy(_ => _ => customKey); } customKey ??= "https://key:80"; - provider.Setup(v => v.GetStrategy(new HttpKey("clientId-standard-hedging", string.Empty))).Returns(noPolicy); - provider.Setup(v => v.GetStrategy(new HttpKey("clientId-standard-hedging-endpoint", customKey))).Returns(noPolicy); + provider.Setup(v => v.GetPipeline(new HttpKey("clientId-standard-hedging", string.Empty))).Returns(noPolicy); + provider.Setup(v => v.GetPipeline(new HttpKey("clientId-standard-hedging-endpoint", customKey))).Returns(noPolicy); using var client = CreateClientWithHandler(); using var request = new HttpRequestMessage(HttpMethod.Get, "https://key:80/discarded"); @@ -227,11 +225,11 @@ public async Task DynamicReloads_Ok() var config = ConfigurationStubFactory.Create( new() { - { "standard:HedgingOptions:MaxHedgedAttempts", "3" } + { "standard:HedgingOptions:MaxHedgedAttempts", "2" } }, out var reloadAction).GetSection("standard"); - Builder.Configure(config).Configure(options => options.HedgingOptions.HedgingDelay = Timeout.InfiniteTimeSpan); + Builder.Configure(config).Configure(options => options.HedgingOptions.Delay = Timeout.InfiniteTimeSpan); SetupRouting(); SetupRoutes(10); @@ -243,7 +241,7 @@ public async Task DynamicReloads_Ok() await client.SendAsync(firstRequest); AssertNoResponse(); - reloadAction(new() { { "standard:HedgingOptions:MaxHedgedAttempts", "7" } }); + reloadAction(new() { { "standard:HedgingOptions:MaxHedgedAttempts", "6" } }); AddResponse(HttpStatusCode.InternalServerError, 7); using var secondRequest = new HttpRequestMessage(HttpMethod.Get, "https://to-be-replaced:1234/some-path?query"); diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/RequestMessageSnapshotStrategyTests.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/RequestMessageSnapshotStrategyTests.cs index 96ed548b751..f1a54374535 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/RequestMessageSnapshotStrategyTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/RequestMessageSnapshotStrategyTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using FluentAssertions; using Microsoft.Extensions.Http.Resilience.Internal; +using Moq; using Polly; using Xunit; @@ -16,7 +17,7 @@ public class RequestMessageSnapshotStrategyTests [Fact] public async Task SendAsync_EnsureSnapshotAttached() { - var strategy = new RequestMessageSnapshotStrategy(); + var strategy = Create(); var context = ResilienceContextPool.Shared.Get(); using var request = new HttpRequestMessage(); context.Properties.Set(ResilienceKeys.RequestMessage, request); @@ -30,11 +31,14 @@ public async Task SendAsync_EnsureSnapshotAttached() context); } + [Fact] public void ExecuteAsync_requestMessageNotFound_Throws() { - var strategy = new RequestMessageSnapshotStrategy(); + var strategy = Create(); strategy.Invoking(s => s.Execute(() => { })).Should().Throw(); } + + private static ResiliencePipeline Create() => new ResiliencePipelineBuilder().AddStrategy(_ => new RequestMessageSnapshotStrategy(), Mock.Of()).Build(); } diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/ValidationHelperTests.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/ValidationHelperTests.cs index cccaa683337..dc7e68292d4 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/ValidationHelperTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/ValidationHelperTests.cs @@ -4,6 +4,7 @@ using System; using FluentAssertions; using Microsoft.Extensions.Http.Resilience.Internal; +using Polly; using Polly.Retry; using Xunit; @@ -17,9 +18,9 @@ public void GetAggregatedDelay_Constant_Ok() ValidationHelper.GetAggregatedDelay( new HttpRetryStrategyOptions { - RetryCount = 10, - BackoffType = RetryBackoffType.Constant, - BaseDelay = TimeSpan.FromSeconds(1), + MaxRetryAttempts = 10, + BackoffType = DelayBackoffType.Constant, + Delay = TimeSpan.FromSeconds(1), }) .Should().Be(TimeSpan.FromSeconds(10)); } @@ -29,10 +30,10 @@ public void GetAggregatedDelay_ExponentialWithJitter_ShouldNotBeRandomized() { var options = new HttpRetryStrategyOptions { - RetryCount = 10, - BackoffType = RetryBackoffType.Exponential, + MaxRetryAttempts = 10, + BackoffType = DelayBackoffType.Exponential, UseJitter = true, - BaseDelay = TimeSpan.FromSeconds(1), + Delay = TimeSpan.FromSeconds(1), }; ValidationHelper @@ -46,10 +47,10 @@ public void GetAggregatedDelay_Overflow_Handled() { var options = new HttpRetryStrategyOptions { - RetryCount = 99, - BackoffType = RetryBackoffType.Exponential, + MaxRetryAttempts = 99, + BackoffType = DelayBackoffType.Exponential, UseJitter = true, - BaseDelay = TimeSpan.FromSeconds(1000), + Delay = TimeSpan.FromSeconds(1000), }; ValidationHelper diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Polly/HttpCircuitBreakerStrategyOptionsTests.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Polly/HttpCircuitBreakerStrategyOptionsTests.cs index 11db30d5573..372b1d5ce14 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Polly/HttpCircuitBreakerStrategyOptionsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Polly/HttpCircuitBreakerStrategyOptionsTests.cs @@ -69,10 +69,10 @@ public async Task ShouldHandleException_DefaultValue_ShouldClassify(Exception ex Assert.Equal(expectedToHandle, shouldHandle); } - private OutcomeArguments CreateArgs(Exception error) - => new(_context, Outcome.FromException(error), default); + private CircuitBreakerPredicateArguments CreateArgs(Exception error) + => new(_context, Outcome.FromException(error)); - private OutcomeArguments CreateArgs(HttpResponseMessage response) - => new(_context, Outcome.FromResult(response), default); + private CircuitBreakerPredicateArguments CreateArgs(HttpResponseMessage response) + => new(_context, Outcome.FromResult(response)); } diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Polly/HttpRetryStrategyOptionsTests.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Polly/HttpRetryStrategyOptionsTests.cs index 0e9bb935391..97645c4ecee 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Polly/HttpRetryStrategyOptionsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Polly/HttpRetryStrategyOptionsTests.cs @@ -35,14 +35,14 @@ public void Ctor_Defaults() { var options = new HttpRetryStrategyOptions(); - options.BackoffType.Should().Be(RetryBackoffType.Exponential); + options.BackoffType.Should().Be(DelayBackoffType.Exponential); options.UseJitter.Should().BeTrue(); - options.RetryCount.Should().Be(3); - options.BaseDelay.Should().Be(TimeSpan.FromSeconds(2)); + options.MaxRetryAttempts.Should().Be(3); + options.Delay.Should().Be(TimeSpan.FromSeconds(2)); options.ShouldHandle.Should().NotBeNull(); options.OnRetry.Should().BeNull(); options.ShouldRetryAfterHeader.Should().BeTrue(); - options.RetryDelayGenerator.Should().NotBeNull(); + options.DelayGenerator.Should().NotBeNull(); } [Theory] @@ -98,24 +98,27 @@ public async Task ShouldRetryAfterHeader_InvalidOutcomes_ShouldReturnZero() var options = new HttpRetryStrategyOptions { ShouldRetryAfterHeader = true }; using var responseMessage = new HttpResponseMessage { }; - Assert.NotNull(options.RetryDelayGenerator); + Assert.NotNull(options.DelayGenerator); - var result = await options.RetryDelayGenerator( + var result = await options.DelayGenerator( new(ResilienceContextPool.Shared.Get(), Outcome.FromResult(responseMessage), - new RetryDelayArguments(0, TimeSpan.Zero))); + 0, + TimeSpan.Zero)); Assert.Equal(result, TimeSpan.Zero); - result = await options.RetryDelayGenerator( + result = await options.DelayGenerator( new(ResilienceContextPool.Shared.Get(), Outcome.FromResult(null), - new RetryDelayArguments(0, TimeSpan.Zero))); + 0, + TimeSpan.Zero)); Assert.Equal(result, TimeSpan.Zero); - result = await options.RetryDelayGenerator( + result = await options.DelayGenerator( new(ResilienceContextPool.Shared.Get(), Outcome.FromException(new InvalidOperationException()), - new RetryDelayArguments(0, TimeSpan.Zero))); + 0, + TimeSpan.Zero)); Assert.Equal(result, TimeSpan.Zero); } @@ -131,10 +134,11 @@ public async Task ShouldRetryAfterHeader_WhenResponseContainsRetryAfterHeader_Sh } }; - var result = await options.RetryDelayGenerator!( + var result = await options.DelayGenerator!( new(ResilienceContextPool.Shared.Get(), Outcome.FromResult(responseMessage), - new RetryDelayArguments(0, TimeSpan.Zero))); + 0, + TimeSpan.Zero)); Assert.Equal(result, TimeSpan.FromSeconds(10)); } @@ -149,10 +153,10 @@ public void GetDelayGenerator_ShouldGetBasedOnShouldRetryAfterHeader(bool should ShouldRetryAfterHeader = shouldRetryAfterHeader }; - Assert.Equal(shouldRetryAfterHeader, options.RetryDelayGenerator != null); + Assert.Equal(shouldRetryAfterHeader, options.DelayGenerator != null); } - private static OutcomeArguments CreateArgs(Outcome outcome) - => new(ResilienceContextPool.Shared.Get(), outcome, new RetryPredicateArguments(0)); + private static RetryPredicateArguments CreateArgs(Outcome outcome) + => new(ResilienceContextPool.Shared.Get(), outcome, 0); } diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.BySelector.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.BySelector.cs index 389fb8d7517..3e7a8a1ef0b 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.BySelector.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.BySelector.cs @@ -26,32 +26,32 @@ public sealed partial class HttpClientBuilderExtensionsTests [InlineData(false, "https://dummy:21/path", "https://dummy:21")] [InlineData(false, "https://dummy", "https://dummy")] [Theory] - public void SelectStrategyByAuthority_Ok(bool standardResilience, string url, string expectedStrategyKey) + public void SelectPipelineByAuthority_Ok(bool standardResilience, string url, string expectedPipelineKey) { _builder.Services.AddFakeRedaction(); var pipelineName = standardResilience ? - _builder.AddStandardResilienceHandler().SelectStrategyByAuthority(DataClassification.Unknown).StrategyName : + _builder.AddStandardResilienceHandler().SelectPipelineByAuthority(DataClassification.Unknown).PipelineName : _builder .AddResilienceHandler("dummy", builder => builder.AddTimeout(TimeSpan.FromSeconds(1))) - .SelectStrategyByAuthority(DataClassification.Unknown).StrategyName; + .SelectPipelineByAuthority(DataClassification.Unknown).PipelineName; - var provider = _builder.Services.BuildServiceProvider().GetStrategyKeyProvider(pipelineName)!; + var provider = _builder.Services.BuildServiceProvider().GetPipelineKeyProvider(pipelineName)!; using var request = new HttpRequestMessage(HttpMethod.Head, url); var key = provider(request); - Assert.Equal(expectedStrategyKey, key); + Assert.Equal(expectedPipelineKey, key); Assert.Same(provider(request), provider(request)); } [Fact] - public void SelectStrategyByAuthority_Ok_NullURL_Throws() + public void SelectPipelineByAuthority_Ok_NullURL_Throws() { _builder.Services.AddFakeRedaction(); - var builder = _builder.AddResilienceHandler("dummy", builder => builder.AddTimeout(TimeSpan.FromSeconds(1))).SelectStrategyByAuthority(DataClassification.Unknown); - var provider = StrategyKeyProviderHelper.GetStrategyKeyProvider(builder.Services.BuildServiceProvider(), builder.StrategyName)!; + var builder = _builder.AddResilienceHandler("dummy", builder => builder.AddTimeout(TimeSpan.FromSeconds(1))).SelectPipelineByAuthority(DataClassification.Unknown); + var provider = PipelineKeyProviderHelper.GetPipelineKeyProvider(builder.Services.BuildServiceProvider(), builder.PipelineName)!; using var request = new HttpRequestMessage(); @@ -59,11 +59,11 @@ public void SelectStrategyByAuthority_Ok_NullURL_Throws() } [Fact] - public void SelectStrategyByAuthority_ErasingRedactor_InvalidOperationException() + public void SelectPipelineByAuthority_ErasingRedactor_InvalidOperationException() { _builder.Services.AddRedaction(); - var builder = _builder.AddResilienceHandler("dummy", builder => builder.AddTimeout(TimeSpan.FromSeconds(1))).SelectStrategyByAuthority(SimpleClassifications.PrivateData); - var provider = StrategyKeyProviderHelper.GetStrategyKeyProvider(builder.Services.BuildServiceProvider(), builder.StrategyName)!; + var builder = _builder.AddResilienceHandler("dummy", builder => builder.AddTimeout(TimeSpan.FromSeconds(1))).SelectPipelineByAuthority(SimpleClassifications.PrivateData); + var provider = PipelineKeyProviderHelper.GetPipelineKeyProvider(builder.Services.BuildServiceProvider(), builder.PipelineName)!; using var request = new HttpRequestMessage(HttpMethod.Get, "https://dummy"); @@ -75,7 +75,7 @@ public void SelectStrategyByAuthority_ErasingRedactor_InvalidOperationException( [InlineData(false, "https://dummy:21/path", "https://")] [InlineData(false, "https://dummy", "https://")] [Theory] - public void SelectStrategyBy_Ok(bool standardResilience, string url, string expectedStrategyKey) + public void SelectPipelineBy_Ok(bool standardResilience, string url, string expectedPipelineKey) { _builder.Services.AddFakeRedaction(); @@ -85,22 +85,22 @@ public void SelectStrategyBy_Ok(bool standardResilience, string url, string expe { pipelineName = _builder .AddResilienceHandler("dummy", builder => builder.AddTimeout(TimeSpan.FromSeconds(1))) - .SelectStrategyBy(_ => r => r.RequestUri!.GetLeftPart(UriPartial.Scheme)).StrategyName; + .SelectPipelineBy(_ => r => r.RequestUri!.GetLeftPart(UriPartial.Scheme)).PipelineName; } else { pipelineName = _builder .AddStandardResilienceHandler() - .SelectStrategyBy(_ => r => r.RequestUri!.GetLeftPart(UriPartial.Scheme)).StrategyName; + .SelectPipelineBy(_ => r => r.RequestUri!.GetLeftPart(UriPartial.Scheme)).PipelineName; } - var provider = _builder.Services.BuildServiceProvider().GetStrategyKeyProvider(pipelineName)!; + var provider = _builder.Services.BuildServiceProvider().GetPipelineKeyProvider(pipelineName)!; using var request = new HttpRequestMessage(HttpMethod.Head, url); var key = provider(request); - Assert.Equal(expectedStrategyKey, key); + Assert.Equal(expectedPipelineKey, key); Assert.NotSame(provider(request), provider(request)); } @@ -109,34 +109,34 @@ public void SelectStrategyBy_Ok(bool standardResilience, string url, string expe [InlineData(false, "https://dummy:21/path", "https://dummy:21")] [InlineData(false, "https://dummy123", "https://dummy123")] [Theory] - public async Task SelectStrategyBy_EnsureResilienceStrategyProviderCall(bool standardResilience, string url, string expectedStrategyKey) + public async Task SelectPipelineBy_EnsureResiliencePipelineProviderCall(bool standardResilience, string url, string expectedPipelineKey) { - var strategyProvider = new Mock>(MockBehavior.Strict); + var provider = new Mock>(MockBehavior.Strict); _builder.Services.AddFakeRedaction(); - _builder.Services.TryAddSingleton(strategyProvider.Object); + _builder.Services.TryAddSingleton(provider.Object); string? pipelineName = null; if (standardResilience) { pipelineName = _builder .AddResilienceHandler("dummy", builder => builder.AddTimeout(TimeSpan.FromSeconds(1))) - .SelectStrategyByAuthority(DataClassification.None).StrategyName; + .SelectPipelineByAuthority(DataClassification.None).PipelineName; } else { pipelineName = _builder .AddStandardResilienceHandler() - .SelectStrategyByAuthority(DataClassification.None).StrategyName; + .SelectPipelineByAuthority(DataClassification.None).PipelineName; } _builder.AddHttpMessageHandler(() => new TestHandlerStub(HttpStatusCode.OK)); - strategyProvider - .Setup(p => p.GetStrategy(new HttpKey(pipelineName, expectedStrategyKey))) - .Returns(NullResilienceStrategy.Instance); + provider + .Setup(p => p.GetPipeline(new HttpKey(pipelineName, expectedPipelineKey))) + .Returns(ResiliencePipeline.Empty); await CreateClient().GetAsync(url); - strategyProvider.VerifyAll(); + provider.VerifyAll(); } } diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.Resilience.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.Resilience.cs index b4b22343285..9eba4b17022 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.Resilience.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.Resilience.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; @@ -21,8 +22,8 @@ using Microsoft.Extensions.Telemetry.Testing.Metering; using Moq; using Polly; -using Polly.Extensions.Telemetry; using Polly.Registry; +using Polly.Telemetry; using Xunit; namespace Microsoft.Extensions.Http.Resilience.Test; @@ -39,8 +40,8 @@ public void AddResilienceHandler_ArgumentValidation() Assert.Throws(() => builder.AddResilienceHandler(string.Empty, _ => { })); Assert.Throws(() => builder.AddResilienceHandler(null!, (_, _) => { })); Assert.Throws(() => builder.AddResilienceHandler(string.Empty, (_, _) => { })); - Assert.Throws(() => builder.AddResilienceHandler("dummy", (Action>)null!)); - Assert.Throws(() => builder.AddResilienceHandler("dummy", (Action, ResilienceHandlerContext>)null!)); + Assert.Throws(() => builder.AddResilienceHandler("dummy", (Action>)null!)); + Assert.Throws(() => builder.AddResilienceHandler("dummy", (Action, ResilienceHandlerContext>)null!)); builder = null; Assert.Throws(() => builder!.AddResilienceHandler("pipeline-name", _ => { })); @@ -58,7 +59,7 @@ public void AddResilienceHandler_EnsureCorrectServicesRegistered() // add twice intentionally builder.AddResilienceHandler("test", ConfigureBuilder); - Assert.Contains(services, s => s.ServiceType == typeof(ResilienceStrategyProvider)); + Assert.Contains(services, s => s.ServiceType == typeof(ResiliencePipelineProvider)); } [Fact] @@ -80,22 +81,10 @@ public void AddResilienceHandler_EnsureServicesNotAddedTwice() public async Task AddResilienceHandler_EnsureFailureResultContext() { using var metricCollector = new MetricCollector(null, "Polly", "resilience-events"); - - var asserted = false; + var enricher = new TestMeteringEnricher(); var services = new ServiceCollection() .AddResilienceEnrichment() - .Configure(options => - { - options.Enrichers.Add(context => - { - var dict = context.Tags.ToDictionary(t => t.Key, t => t.Value); - dict.Should().ContainKey("failure-reason").WhoseValue.Should().Be("500"); - dict.Should().ContainKey("failure-summary").WhoseValue.Should().Be("InternalServerError"); - dict.Should().ContainKey("failure-source").WhoseValue.Should().Be(TelemetryConstants.Unknown); - - asserted = true; - }); - }); + .Configure(options => options.MeteringEnrichers.Add(enricher)); var clientBuilder = services .AddHttpClient("client") @@ -104,8 +93,8 @@ public async Task AddResilienceHandler_EnsureFailureResultContext() .Configure(options => { options.RetryOptions.ShouldHandle = _ => PredicateResult.True; - options.RetryOptions.RetryCount = 1; - options.RetryOptions.BaseDelay = TimeSpan.Zero; + options.RetryOptions.MaxRetryAttempts = 1; + options.RetryOptions.Delay = TimeSpan.Zero; }); var client = services.BuildServiceProvider().GetRequiredService().CreateClient("client"); @@ -113,7 +102,10 @@ public async Task AddResilienceHandler_EnsureFailureResultContext() using var response = await client.SendAsync(request); - asserted.Should().BeTrue(); + var lookup = enricher.Tags.ToLookup(t => t.Key, t => t.Value); + lookup["failure-reason"].Should().Contain("500"); + lookup["failure-summary"].Should().Contain("InternalServerError"); + lookup["failure-source"].Should().Contain(TelemetryConstants.Unknown); } [Fact] @@ -128,7 +120,7 @@ public async Task AddResilienceHandler_EnsureResilienceHandlerContext() context.InstanceName.Should().Be("dummy-key"); verified = true; }) - .SelectStrategyBy(_ => _ => "dummy-key"); + .SelectPipelineBy(_ => _ => "dummy-key"); _builder.AddHttpMessageHandler(() => new TestHandlerStub(HttpStatusCode.InternalServerError)); @@ -143,12 +135,12 @@ public void AddResilienceHandler_EnsureCorrectRegistryOptions() IHttpClientBuilder? builder = services.AddHttpClient("client"); builder.AddResilienceHandler("test", ConfigureBuilder); - var registryOptions = builder.Services.BuildServiceProvider().GetRequiredService>>().Value; + var registryOptions = builder.Services.BuildServiceProvider().GetRequiredService>>().Value; registryOptions.BuilderComparer.Equals(new HttpKey("A", "1"), new HttpKey("A", "2")).Should().BeTrue(); registryOptions.BuilderComparer.Equals(new HttpKey("A", "1"), new HttpKey("B", "1")).Should().BeFalse(); - registryOptions.StrategyComparer.Equals(new HttpKey("A", "1"), new HttpKey("A", "1")).Should().BeTrue(); - registryOptions.StrategyComparer.Equals(new HttpKey("A", "1"), new HttpKey("A", "2")).Should().BeFalse(); + registryOptions.PipelineComparer.Equals(new HttpKey("A", "1"), new HttpKey("A", "1")).Should().BeTrue(); + registryOptions.PipelineComparer.Equals(new HttpKey("A", "1"), new HttpKey("A", "2")).Should().BeFalse(); registryOptions.BuilderNameFormatter(new HttpKey("A", "1")).Should().Be("A"); registryOptions.InstanceNameFormatter!(new HttpKey("A", "1")).Should().Be("1"); @@ -164,18 +156,18 @@ public enum PolicyType [InlineData(true)] [InlineData(false)] [Theory] - public async Task AddResilienceHandler_EnsureProperStrategyInstanceRetrieved(bool bySelector) + public async Task AddResilienceHandler_EnsureProperPipelineInstanceRetrieved(bool bySelector) { // arrange - var resilienceProvider = new Mock>(MockBehavior.Strict); + var resilienceProvider = new Mock>(MockBehavior.Strict); var services = new ServiceCollection().AddLogging().RegisterMetering().AddFakeRedaction(); services.AddSingleton(resilienceProvider.Object); var builder = services.AddHttpClient("client"); var pipelineBuilder = builder.AddResilienceHandler("dummy", ConfigureBuilder); - var expectedStrategyName = "client-dummy"; + var expectedPipelineKey = "client-dummy"; if (bySelector) { - pipelineBuilder.SelectStrategyByAuthority(DataClassification.Unknown); + pipelineBuilder.SelectPipelineByAuthority(DataClassification.Unknown); } builder.AddHttpMessageHandler(() => new TestHandlerStub(HttpStatusCode.OK)); @@ -184,14 +176,14 @@ public async Task AddResilienceHandler_EnsureProperStrategyInstanceRetrieved(boo if (bySelector) { resilienceProvider - .Setup(v => v.GetStrategy(new HttpKey(expectedStrategyName, "https://dummy1"))) - .Returns(NullResilienceStrategy.Instance); + .Setup(v => v.GetPipeline(new HttpKey(expectedPipelineKey, "https://dummy1"))) + .Returns(ResiliencePipeline.Empty); } else { resilienceProvider - .Setup(v => v.GetStrategy(new HttpKey(expectedStrategyName, string.Empty))) - .Returns(NullResilienceStrategy.Instance); + .Setup(v => v.GetPipeline(new HttpKey(expectedPipelineKey, string.Empty))) + .Returns(ResiliencePipeline.Empty); } var client = provider.GetRequiredService().CreateClient("client"); @@ -204,11 +196,11 @@ public async Task AddResilienceHandler_EnsureProperStrategyInstanceRetrieved(boo } [Fact] - public async Task AddResilienceHandlerBySelector_EnsureResilienceStrategyProviderCalled() + public async Task AddResilienceHandlerBySelector_EnsureResiliencePipelineProviderCalled() { // arrange var services = new ServiceCollection().AddLogging().RegisterMetering(); - var providerMock = new Mock>(MockBehavior.Strict); + var providerMock = new Mock>(MockBehavior.Strict); services.AddSingleton(providerMock.Object); var pipelineName = string.Empty; @@ -219,13 +211,13 @@ public async Task AddResilienceHandlerBySelector_EnsureResilienceStrategyProvide clientBuilder.AddHttpMessageHandler(() => new TestHandlerStub(HttpStatusCode.OK)); providerMock - .Setup(v => v.GetStrategy(new HttpKey(pipelineName, string.Empty))) - .Returns(NullResilienceStrategy.Instance) + .Setup(v => v.GetPipeline(new HttpKey(pipelineName, string.Empty))) + .Returns(ResiliencePipeline.Empty) .Verifiable(); var provider = services.BuildServiceProvider(); var client = provider.GetRequiredService().CreateClient("client"); - var pipelineProvider = provider.GetRequiredService>(); + var pipelineProvider = provider.GetRequiredService>(); // act await client.GetAsync("https://dummy1"); @@ -241,12 +233,12 @@ public void AddResilienceHandler_AuthoritySelectorAndNotConfiguredRedaction_Ensu var clientBuilder = new ServiceCollection().AddLogging().RegisterMetering().AddRedaction() .AddHttpClient("my-client") .AddResilienceHandler("my-pipeline", ConfigureBuilder) - .SelectStrategyByAuthority(SimpleClassifications.PrivateData); + .SelectPipelineByAuthority(SimpleClassifications.PrivateData); var factory = clientBuilder.Services.BuildServiceProvider().GetRequiredService(); var error = Assert.Throws(() => factory.CreateClient("my-client")); - Assert.Equal("The redacted strategy key is an empty string and cannot be used for the strategy selection. Is redaction correctly configured?", error.Message); + Assert.Equal("The redacted pipeline key is an empty string and cannot be used for the pipeline selection. Is redaction correctly configured?", error.Message); } [Fact] @@ -256,12 +248,22 @@ public void AddResilienceHandler_AuthorityByCustomSelector_NotValidated() var clientBuilder = new ServiceCollection().AddLogging().RegisterMetering().AddRedaction() .AddHttpClient("my-client") .AddResilienceHandler("my-pipeline", ConfigureBuilder) - .SelectStrategyBy(_ => _ => string.Empty); + .SelectPipelineBy(_ => _ => string.Empty); var factory = clientBuilder.Services.BuildServiceProvider().GetRequiredService(); Assert.NotNull(factory.CreateClient("my-client")); } - private void ConfigureBuilder(CompositeStrategyBuilder builder) => builder.AddTimeout(TimeSpan.FromSeconds(1)); + private void ConfigureBuilder(ResiliencePipelineBuilder builder) => builder.AddTimeout(TimeSpan.FromSeconds(1)); + + private class TestMeteringEnricher : MeteringEnricher + { + public List> Tags { get; } = new(); + + public override void Enrich(in EnrichmentContext context) + { + Tags.AddRange(context.Tags); + } + } } diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.Standard.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.Standard.cs index a41089fc7f1..0dbdc38c0e1 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.Standard.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.Standard.cs @@ -122,7 +122,7 @@ public void AddStandardResilienceHandler_ConfigurationPropertyWithTypo_Throws(Me AddStandardResilienceHandler(mode, builder, _invalidConfigurationSection, options => { }); - Assert.Throws(() => HttpClientBuilderExtensionsTests.GetStrategy(builder.Services, $"test-standard")); + Assert.Throws(() => HttpClientBuilderExtensionsTests.GetPipeline(builder.Services, $"test-standard")); } [Fact] @@ -134,12 +134,11 @@ public void AddStandardResilienceHandler_EnsureCorrectStrategies() .AddHttpClient("test") .AddStandardResilienceHandler() .Services.BuildServiceProvider() - .GetRequiredService>(); + .GetRequiredService>(); - var descriptor = provider.GetStrategy(new HttpKey("test-standard", string.Empty)).GetInnerStrategies(); + var descriptor = provider.GetPipeline(new HttpKey("test-standard", string.Empty)).GetPipelineDescriptor(); descriptor.Strategies.Should().HaveCount(5); - descriptor.HasTelemetry.Should().BeTrue(); descriptor.IsReloadable.Should().BeTrue(); descriptor.Strategies[0].Options.Should().BeOfType(); @@ -165,11 +164,11 @@ public void AddStandardResilienceHandler_EnsureValidated(bool wholePipeline) } else { - options.RetryOptions.RetryCount = -3; + options.RetryOptions.MaxRetryAttempts = -3; } }); - Assert.Throws(() => GetStrategy(builder.Services, $"test-standard")); + Assert.Throws(() => GetPipeline(builder.Services, $"test-standard")); } [InlineData(MethodArgs.None)] @@ -187,7 +186,7 @@ public void AddStandardResilienceHandler_EnsureConfigured(MethodArgs mode) AddStandardResilienceHandler(mode, builder, _validConfigurationSection, options => { }); - var pipeline = GetStrategy(builder.Services, $"test-standard"); + var pipeline = GetPipeline(builder.Services, $"test-standard"); Assert.NotNull(pipeline); } @@ -199,14 +198,14 @@ public async Task DynamicReloads_Ok() var config = ConfigurationStubFactory.Create( new() { - { "standard:RetryOptions:RetryCount", "6" } + { "standard:RetryOptions:MaxRetryAttempts", "6" } }, out var reloadAction).GetSection("standard"); _builder.AddStandardResilienceHandler().Configure(config).Configure(options => { - options.RetryOptions.BaseDelay = TimeSpan.Zero; - options.RetryOptions.BackoffType = RetryBackoffType.Constant; + options.RetryOptions.Delay = TimeSpan.Zero; + options.RetryOptions.BackoffType = DelayBackoffType.Constant; }); _builder.AddHttpMessageHandler(() => new TestHandlerStub((r, _) => { @@ -221,7 +220,7 @@ public async Task DynamicReloads_Ok() requests.Should().HaveCount(7); requests.Clear(); - reloadAction(new() { { "standard:RetryOptions:RetryCount", "10" } }); + reloadAction(new() { { "standard:RetryOptions:MaxRetryAttempts", "10" } }); await client.GetAsync("https://dummy"); requests.Should().HaveCount(11); @@ -251,10 +250,10 @@ private static void AddStandardResilienceHandler( }; } - private static ResilienceStrategy GetStrategy(IServiceCollection services, string name) + private static ResiliencePipeline GetPipeline(IServiceCollection services, string name) { - var provider = services.BuildServiceProvider().GetRequiredService>(); + var provider = services.BuildServiceProvider().GetRequiredService>(); - return provider.GetStrategy(new HttpKey(name, string.Empty)); + return provider.GetPipeline(new HttpKey(name, string.Empty)); } } diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpStandardResilienceOptionsCustomValidatorTests.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpStandardResilienceOptionsCustomValidatorTests.cs index 84b93e700a4..7fd22a7ef5c 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpStandardResilienceOptionsCustomValidatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpStandardResilienceOptionsCustomValidatorTests.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.Http.Resilience; #endif using Microsoft.Extensions.Http.Resilience.Internal.Validators; +using Polly; using Polly.Retry; using Xunit; @@ -60,9 +61,9 @@ public static IEnumerable GetOptions_ValidOptions_EnsureNoErrors_Data yield return new object[] { options }; options = new HttpStandardResilienceOptions(); - options.RetryOptions.RetryCount = 1; - options.RetryOptions.BackoffType = RetryBackoffType.Linear; - options.RetryOptions.BaseDelay = options.TotalRequestTimeoutOptions.Timeout; + options.RetryOptions.MaxRetryAttempts = 1; + options.RetryOptions.BackoffType = DelayBackoffType.Linear; + options.RetryOptions.Delay = options.TotalRequestTimeoutOptions.Timeout; yield return new object[] { options }; } } @@ -90,7 +91,7 @@ public static IEnumerable GetOptions_InvalidOptions_EnsureErrors_Data yield return new object[] { options }; options = new HttpStandardResilienceOptions(); - options.RetryOptions.BaseDelay = TimeSpan.FromDays(1); + options.RetryOptions.Delay = TimeSpan.FromDays(1); yield return new object[] { options }; options = new HttpStandardResilienceOptions(); diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/StrategyNameHelperTest.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/PipelineNameHelperTest.cs similarity index 78% rename from test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/StrategyNameHelperTest.cs rename to test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/PipelineNameHelperTest.cs index 5f84ff8704c..0d15c5e1d7f 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/StrategyNameHelperTest.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/PipelineNameHelperTest.cs @@ -5,11 +5,12 @@ using Xunit; namespace Microsoft.Extensions.Http.Resilience.Test.Resilience; -public class StrategyNameHelperTest + +public class PipelineNameHelperTest { [Fact] public void GetPipelineName_Ok() { - Assert.Equal("client-pipeline", StrategyNameHelper.GetName("client", "pipeline")); + Assert.Equal("client-pipeline", PipelineNameHelper.GetName("client", "pipeline")); } } diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/ResilienceHandlerTest.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/ResilienceHandlerTest.cs index 4e631eb55ba..873b04efc59 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/ResilienceHandlerTest.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/ResilienceHandlerTest.cs @@ -23,7 +23,7 @@ public class ResilienceHandlerTest [Theory] public async Task SendAsync_EnsureRequestMetadataFlows(bool resilienceContextSet) { - using var handler = new ResilienceHandler(_ => NullResilienceStrategy.Instance); + using var handler = new ResilienceHandler(_ => ResiliencePipeline.Empty); using var invoker = new HttpMessageInvoker(handler); using var request = new HttpRequestMessage(); @@ -57,7 +57,7 @@ public async Task SendAsync_EnsureRequestMetadataFlows(bool resilienceContextSet [Theory] public async Task SendAsync_EnsureExecutionContext(bool executionContextSet) { - using var handler = new ResilienceHandler(_ => NullResilienceStrategy.Instance); + using var handler = new ResilienceHandler(_ => ResiliencePipeline.Empty); using var invoker = new HttpMessageInvoker(handler); using var request = new HttpRequestMessage(); @@ -85,7 +85,7 @@ public async Task SendAsync_EnsureExecutionContext(bool executionContextSet) [Theory] public async Task SendAsync_EnsureInvoker(bool executionContextSet) { - using var handler = new ResilienceHandler(_ => NullResilienceStrategy.Instance); + using var handler = new ResilienceHandler(_ => ResiliencePipeline.Empty); using var invoker = new HttpMessageInvoker(handler); using var request = new HttpRequestMessage(); @@ -111,7 +111,7 @@ public async Task SendAsync_EnsureInvoker(bool executionContextSet) public async Task SendAsync_EnsureCancellationTokenFlowsToResilienceContext() { using var source = new CancellationTokenSource(); - using var handler = new ResilienceHandler(_ => NullResilienceStrategy.Instance); + using var handler = new ResilienceHandler(_ => ResiliencePipeline.Empty); using var invoker = new HttpMessageInvoker(handler); using var request = new HttpRequestMessage(); @@ -130,7 +130,7 @@ public async Task SendAsync_EnsureCancellationTokenFlowsToResilienceContext() [Fact] public async Task SendAsync_Exception_EnsureRethrown() { - using var handler = new ResilienceHandler(_ => NullResilienceStrategy.Instance); + using var handler = new ResilienceHandler(_ => ResiliencePipeline.Empty); using var invoker = new HttpMessageInvoker(handler); using var request = new HttpRequestMessage(); diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Routing/RoutingResilienceStrategyTests.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Routing/RoutingResilienceStrategyTests.cs index 5ab29767ba3..341d32087f8 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Routing/RoutingResilienceStrategyTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Routing/RoutingResilienceStrategyTests.cs @@ -17,7 +17,7 @@ public class RoutingResilienceStrategyTests [Fact] public void NoRequestMessage_Throws() { - RoutingResilienceStrategy strategy = new RoutingResilienceStrategy(() => Mock.Of()); + var strategy = Create(() => Mock.Of()); strategy.Invoking(s => s.Execute(() => { })).Should().Throw().WithMessage("The HTTP request message was not found in the resilience context."); } @@ -27,10 +27,14 @@ public void NoRoutingProvider_Ok() { using var request = new HttpRequestMessage(); - RoutingResilienceStrategy strategy = new RoutingResilienceStrategy(null); + var strategy = Create(null); var context = ResilienceContextPool.Shared.Get(); context.Properties.Set(ResilienceKeys.RequestMessage, request); strategy.Invoking(s => s.Execute(_ => { }, context)).Should().NotThrow(); } + + private static ResiliencePipeline Create(Func? provider) => + new ResiliencePipelineBuilder().AddStrategy(_ => new RoutingResilienceStrategy(provider), Mock.Of()).Build(); + } diff --git a/test/Libraries/Microsoft.Extensions.Resilience.Tests/Resilience/ResilienceMeteringEnricherTests.cs b/test/Libraries/Microsoft.Extensions.Resilience.Tests/Resilience/ResilienceMeteringEnricherTests.cs new file mode 100644 index 00000000000..94ec8a1849d --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Resilience.Tests/Resilience/ResilienceMeteringEnricherTests.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Microsoft.Extensions.Diagnostics.ExceptionSummarization; +using Microsoft.Extensions.Http.Telemetry; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Resilience; +using Microsoft.Extensions.Resilience.Internal; +using Moq; +using Polly; +using Polly.Telemetry; +using Xunit; + +namespace Microsoft.Extensions.Resilience.Test.Resilience; + +public class ResilienceMeteringEnricherTests +{ + private readonly Mock _summarizer = new(MockBehavior.Strict); + private readonly List _outgoingRequestContexts = new(); + private readonly FailureEventMetricsOptions _options = new(); + + private List> _tags = new(); + + private IReadOnlyDictionary Tags => _tags.ToDictionary(v => v.Key, v => v.Value); + + [Fact] + public void AddResilienceEnrichment_NoOutcome_EnsureDimensions() + { + CreateSut().Enrich(CreateEnrichmentContext(null)); + + _tags.Should().BeEmpty(); + } + + [Fact] + public void AddResilienceEnrichment_Exception_EnsureDimensions() + { + _summarizer.Setup(v => v.Summarize(It.IsAny())).Returns(new ExceptionSummary("type", "desc", "details")); + + CreateSut().Enrich(CreateEnrichmentContext(Outcome.FromException(new InvalidOperationException { Source = "my-source" }))); + + Tags["failure-reason"].Should().Be("InvalidOperationException"); + Tags["failure-summary"].Should().Be("type:desc:details"); + Tags["failure-source"].Should().Be("my-source"); + } + + [Fact] + public void AddResilienceEnrichment_Outcome_EnsureDimensions() + { + _options.ConfigureFailureResultContext(v => FailureResultContext.Create("my-source", "my-reason", v)); + + CreateSut().Enrich(CreateEnrichmentContext(Outcome.FromResult("string-result"))); + + Tags["failure-source"].Should().Be("my-source"); + Tags["failure-reason"].Should().Be("my-reason"); + Tags["failure-summary"].Should().Be("string-result"); + } + + [Fact] + public void AddResilienceEnrichment_RequestMetadata_EnsureDimensions() + { + CreateSut().Enrich(CreateEnrichmentContext( + Outcome.FromResult("string-result"), + context => context.SetRequestMetadata(new RequestMetadata { RequestName = "my-req", DependencyName = "my-dep" }))); + + Tags["dep-name"].Should().Be("my-dep"); + Tags["req-name"].Should().Be("my-req"); + } + + [Fact] + public void AddResilienceEnrichment_RequestMetadataFromOutgoingRequestContext_EnsureDimensions() + { + var requestMetadata = new RequestMetadata { RequestName = "my-req", DependencyName = "my-dep" }; + _outgoingRequestContexts.Add(Mock.Of(v => v.RequestMetadata == requestMetadata)); + + CreateSut().Enrich(CreateEnrichmentContext()); + + Tags["dep-name"].Should().Be("my-dep"); + Tags["req-name"].Should().Be("my-req"); + } + + private EnrichmentContext CreateEnrichmentContext(Outcome? outcome = null, Action? configure = null) + { + var source = new ResilienceTelemetrySource("A", "B", "C"); + var context = ResilienceContextPool.Shared.Get(); + configure?.Invoke(context); + + return new EnrichmentContext( + new TelemetryEventArguments( + source, + new ResilienceEvent(ResilienceEventSeverity.Warning, "dummy"), + context, + new object(), + outcome), + _tags); + } + + private ResilienceMeteringEnricher CreateSut() => new(Options.Options.Create(_options), _outgoingRequestContexts, _summarizer.Object); +} diff --git a/test/Libraries/Microsoft.Extensions.Resilience.Tests/Resilience/ResilienceServiceCollectionExtensionsTests.cs b/test/Libraries/Microsoft.Extensions.Resilience.Tests/Resilience/ResilienceServiceCollectionExtensionsTests.cs index bc3f85a7062..3d0386cf808 100644 --- a/test/Libraries/Microsoft.Extensions.Resilience.Tests/Resilience/ResilienceServiceCollectionExtensionsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Resilience.Tests/Resilience/ResilienceServiceCollectionExtensionsTests.cs @@ -3,65 +3,42 @@ using System; using System.Collections.Generic; +using System.Linq; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Diagnostics.ExceptionSummarization; -using Microsoft.Extensions.Http.Telemetry; +using Microsoft.Extensions.Options; using Microsoft.Extensions.Resilience; -using Microsoft.Extensions.Telemetry.Testing.Metering; +using Microsoft.Extensions.Resilience.Internal; using Moq; -using Polly; -using Polly.Registry; using Polly.Telemetry; using Xunit; namespace Microsoft.Extensions.Resilience.Test.Resilience; -#pragma warning disable CA1063 // Implement IDisposable Correctly -#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize - -public class ResilienceServiceCollectionExtensionsTests : IDisposable +public class ResilienceServiceCollectionExtensionsTests { private readonly Mock _summarizer = new(MockBehavior.Strict); - private ResilienceStrategyTelemetry? _telemetry; - private MetricCollector _metricCollector; private IServiceCollection _services; public ResilienceServiceCollectionExtensionsTests() { - _metricCollector = new MetricCollector(null, "Polly", "resilience-events"); - _services = new ServiceCollection() - .AddResilienceEnrichment() - .AddResilienceStrategy("dummy", builder => - { - builder.AddStrategy(context => - { - _telemetry = context.Telemetry; - return Mock.Of(); - }, - Mock.Of()); - }); - + _services = new ServiceCollection().AddResilienceEnrichment(); _services.TryAddSingleton(_summarizer.Object); } - public void Dispose() => _metricCollector.Dispose(); - - private IReadOnlyDictionary Tags => _metricCollector.LastMeasurement!.Tags; - [Fact] - public void AddResilienceEnrichment_NoOutcome_EnsureDimensions() + public void AddResilienceEnrichment_EnsureMeteringEnricherRegistered() { - Build(); - _telemetry!.Report(new ResilienceEvent(ResilienceEventSeverity.Information, "dummy-event"), ResilienceContextPool.Shared.Get(), string.Empty); + var count = _services.Count; + + _services.AddResilienceEnrichment(); - Tags["failure-reason"].Should().BeNull(); - Tags["failure-source"].Should().BeNull(); - Tags["failure-summary"].Should().BeNull(); - Tags["dep-name"].Should().BeNull(); - Tags["req-name"].Should().BeNull(); + var enrichers = _services.BuildServiceProvider().GetRequiredService>().Value.MeteringEnrichers; + enrichers.Should().HaveCount(1); + enrichers.Single().Should().BeOfType(); } [Fact] @@ -75,63 +52,12 @@ public void AddResilienceEnrichment_Twice_NoNewServices() } [Fact] - public void AddResilienceEnrichment_Exception_EnsureDimensions() - { - _summarizer.Setup(v => v.Summarize(It.IsAny())).Returns(new ExceptionSummary("type", "desc", "details")); - - Build(); - _telemetry!.Report( - new ResilienceEvent(ResilienceEventSeverity.Information, "dummy-event"), - new OutcomeArguments(ResilienceContextPool.Shared.Get(), Outcome.FromException(new InvalidOperationException { Source = "my-source" }), string.Empty)); - - Tags["failure-reason"].Should().Be("InvalidOperationException"); - Tags["failure-summary"].Should().Be("type:desc:details"); - Tags["failure-source"].Should().Be("my-source"); - } - - [Fact] - public void AddResilienceEnrichment_Outcome_EnsureDimensions() - { - _services.ConfigureFailureResultContext(v => FailureResultContext.Create("my-source", "my-reason", v)); - - Build(); - _telemetry!.Report( - new ResilienceEvent(ResilienceEventSeverity.Information, "dummy-event"), - new OutcomeArguments(ResilienceContextPool.Shared.Get(), Outcome.FromResult("string-result"), string.Empty)); - - Tags["failure-source"].Should().Be("my-source"); - Tags["failure-reason"].Should().Be("my-reason"); - Tags["failure-summary"].Should().Be("string-result"); - } - - [Fact] - public void AddResilienceEnrichment_RequestMetadata_EnsureDimensions() + public void ConfigureFailureResultContext_Ok() { - var context = ResilienceContextPool.Shared.Get(); - context.SetRequestMetadata(new RequestMetadata { RequestName = "my-req", DependencyName = "my-dep" }); - - Build(); - _telemetry!.Report(new ResilienceEvent(ResilienceEventSeverity.Information, "dummy-event"), context, string.Empty); - - Tags["dep-name"].Should().Be("my-dep"); - Tags["req-name"].Should().Be("my-req"); - } - - [Fact] - public void AddResilienceEnrichment_RequestMetadataFromOutgoingRequestContext_EnsureDimensions() - { - var requestMetadata = new RequestMetadata { RequestName = "my-req", DependencyName = "my-dep" }; - _services.TryAddSingleton(Mock.Of(v => v.RequestMetadata == requestMetadata)); - - Build(); - _telemetry!.Report(new ResilienceEvent(ResilienceEventSeverity.Information, "dummy-event"), ResilienceContextPool.Shared.Get(), string.Empty); - - Tags["dep-name"].Should().Be("my-dep"); - Tags["req-name"].Should().Be("my-req"); - } + var count = _services.Count; + _services.ConfigureFailureResultContext(_ => FailureResultContext.Create("dummy", "dummy", "dummy")); + var factories = _services.BuildServiceProvider().GetRequiredService>().Value.Factories; - private void Build() - { - _services.BuildServiceProvider().GetRequiredService>().GetStrategy("dummy"); + factories[typeof(int)](10).FailureReason.Should().Be("dummy"); } } From 36464d94b5a809cbfe6b177000c2406c35d168a5 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Thu, 31 Aug 2023 14:35:33 +0200 Subject: [PATCH 2/4] Upgrade to beta.1 --- eng/packages/General.props | 8 +-- eng/packages/TestOnly.props | 2 +- .../Internal/ValidationHelper.cs | 48 --------------- .../Polly/HttpRetryStrategyOptions.cs | 4 +- .../Resilience/Internal/ResilienceHandler.cs | 2 +- ...tandardResilienceOptionsCustomValidator.cs | 12 ---- .../Hedging/StandardHedgingTests.cs | 2 +- .../Internal/ValidationHelperTests.cs | 61 ------------------- .../Polly/HttpRetryStrategyOptionsTests.cs | 21 +++---- ...ClientBuilderExtensionsTests.Resilience.cs | 2 +- ...tpClientBuilderExtensionsTests.Standard.cs | 4 +- ...rdResilienceOptionsCustomValidatorTests.cs | 6 +- 12 files changed, 22 insertions(+), 150 deletions(-) delete mode 100644 src/Libraries/Microsoft.Extensions.Http.Resilience/Internal/ValidationHelper.cs delete mode 100644 test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/ValidationHelperTests.cs diff --git a/eng/packages/General.props b/eng/packages/General.props index 4c0f51a09d8..2b191f99203 100644 --- a/eng/packages/General.props +++ b/eng/packages/General.props @@ -38,10 +38,10 @@ - - - - + + + + diff --git a/eng/packages/TestOnly.props b/eng/packages/TestOnly.props index 59c7a510af4..5343aeeb35e 100644 --- a/eng/packages/TestOnly.props +++ b/eng/packages/TestOnly.props @@ -8,7 +8,7 @@ - + diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Internal/ValidationHelper.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Internal/ValidationHelper.cs deleted file mode 100644 index 95952c77eb1..00000000000 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Internal/ValidationHelper.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Threading.Tasks; -using Polly; -using Polly.Retry; - -namespace Microsoft.Extensions.Http.Resilience.Internal; - -internal static class ValidationHelper -{ - public static TimeSpan GetAggregatedDelay(RetryStrategyOptions options) - { - // Instead of re-implementing the calculations of delays we can just - // execute the retry strategy and aggregate the delays by using the RetryDelayGenerator - // callback that receives the delay hint for each attempt. - - try - { - var aggregatedDelay = TimeSpan.Zero; - new ResiliencePipelineBuilder().AddRetry(new() - { - MaxRetryAttempts = options.MaxRetryAttempts, - Delay = options.Delay, - BackoffType = options.BackoffType, - ShouldHandle = _ => PredicateResult.True, // always retry until all retries are exhausted - DelayGenerator = args => - { - // the delay hint is calculated for this attempt by the retry strategy - aggregatedDelay += args.DelayHint; - - // return zero delay, so no waiting - return new ValueTask(TimeSpan.Zero); - }, - Randomizer = () => 1.0 // disable randomization so the output is always the same - }) - .Build() - .Execute(static () => { }); // this executes all retries and we aggregate the delays immediately - - return aggregatedDelay; - } - catch (OverflowException) - { - return TimeSpan.MaxValue; - } - } -} diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpRetryStrategyOptions.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpRetryStrategyOptions.cs index 7d889b606cd..d2ceaf3b51f 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpRetryStrategyOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpRetryStrategyOptions.cs @@ -54,8 +54,8 @@ public bool ShouldRetryAfterHeader { DelayGenerator = args => args.Outcome.Result switch { - HttpResponseMessage response when RetryAfterHelper.TryParse(response, TimeProvider.System, out var retryAfter) => new ValueTask(retryAfter), - _ => new ValueTask(args.DelayHint) + HttpResponseMessage response when RetryAfterHelper.TryParse(response, TimeProvider.System, out var retryAfter) => new ValueTask(retryAfter), + _ => new ValueTask((TimeSpan?)null) }; } else diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ResilienceHandler.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ResilienceHandler.cs index 050b64fe7a5..fb3f0cfb7a5 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ResilienceHandler.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/ResilienceHandler.cs @@ -65,7 +65,7 @@ static async (context, state) => (instance: this, request)) .ConfigureAwait(context.ContinueOnCapturedContext); - outcome.EnsureSuccess(); + outcome.ThrowIfException(); return outcome.Result!; } diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/Validators/HttpStandardResilienceOptionsCustomValidator.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/Validators/HttpStandardResilienceOptionsCustomValidator.cs index f28e463404e..85a90836449 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/Validators/HttpStandardResilienceOptionsCustomValidator.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/Validators/HttpStandardResilienceOptionsCustomValidator.cs @@ -30,18 +30,6 @@ public ValidateOptionsResult Validate(string? name, HttpStandardResilienceOption $"Attempt Timeout: {options.AttemptTimeoutOptions.Timeout.TotalSeconds}s"); } - if (options.RetryOptions.MaxRetryAttempts > 0) - { - TimeSpan retrySum = ValidationHelper.GetAggregatedDelay(options.RetryOptions); - - if (retrySum > options.TotalRequestTimeoutOptions.Timeout) - { - builder.AddError($"The cumulative delay of the retry strategy cannot be larger than total request timeout policy interval. " + - $"Cumulative Delay: {retrySum.TotalSeconds}s," + - $"Total Request Timeout: {options.TotalRequestTimeoutOptions.Timeout.TotalSeconds}s"); - } - } - return builder.Build(); } diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/StandardHedgingTests.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/StandardHedgingTests.cs index 83cea981ff9..c5030c7bb72 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/StandardHedgingTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/StandardHedgingTests.cs @@ -112,7 +112,7 @@ public void ActionGenerator_Ok() var secondary = ResilienceContextPool.Shared.Get(); using var response = new HttpResponseMessage(HttpStatusCode.OK); - var args = new HedgingActionGeneratorArguments(primary, secondary, 0, _ => Outcome.FromResultAsTask(response)); + var args = new HedgingActionGeneratorArguments(primary, secondary, 0, _ => Outcome.FromResultAsValueTask(response)); generator.Invoking(g => g(args)).Should().Throw().WithMessage("Request message snapshot is not attached to the resilience context."); using var request = new HttpRequestMessage(); diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/ValidationHelperTests.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/ValidationHelperTests.cs deleted file mode 100644 index dc7e68292d4..00000000000 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/ValidationHelperTests.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using FluentAssertions; -using Microsoft.Extensions.Http.Resilience.Internal; -using Polly; -using Polly.Retry; -using Xunit; - -namespace Microsoft.Extensions.Http.Resilience.Test.Internal; - -public class ValidationHelperTests -{ - [Fact] - public void GetAggregatedDelay_Constant_Ok() - { - ValidationHelper.GetAggregatedDelay( - new HttpRetryStrategyOptions - { - MaxRetryAttempts = 10, - BackoffType = DelayBackoffType.Constant, - Delay = TimeSpan.FromSeconds(1), - }) - .Should().Be(TimeSpan.FromSeconds(10)); - } - - [Fact] - public void GetAggregatedDelay_ExponentialWithJitter_ShouldNotBeRandomized() - { - var options = new HttpRetryStrategyOptions - { - MaxRetryAttempts = 10, - BackoffType = DelayBackoffType.Exponential, - UseJitter = true, - Delay = TimeSpan.FromSeconds(1), - }; - - ValidationHelper - .GetAggregatedDelay(options) - .Should() - .Be(ValidationHelper.GetAggregatedDelay(options)); - } - - [Fact] - public void GetAggregatedDelay_Overflow_Handled() - { - var options = new HttpRetryStrategyOptions - { - MaxRetryAttempts = 99, - BackoffType = DelayBackoffType.Exponential, - UseJitter = true, - Delay = TimeSpan.FromSeconds(1000), - }; - - ValidationHelper - .GetAggregatedDelay(options) - .Should() - .Be(TimeSpan.MaxValue); - } -} diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Polly/HttpRetryStrategyOptionsTests.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Polly/HttpRetryStrategyOptionsTests.cs index 97645c4ecee..56fb80c56f9 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Polly/HttpRetryStrategyOptionsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Polly/HttpRetryStrategyOptionsTests.cs @@ -93,7 +93,7 @@ public async Task ShouldHandleException_DefaultInstance_ShouldClassify(Exception } [Fact] - public async Task ShouldRetryAfterHeader_InvalidOutcomes_ShouldReturnZero() + public async Task ShouldRetryAfterHeader_InvalidOutcomes_ShouldReturnNull() { var options = new HttpRetryStrategyOptions { ShouldRetryAfterHeader = true }; using var responseMessage = new HttpResponseMessage { }; @@ -103,23 +103,21 @@ public async Task ShouldRetryAfterHeader_InvalidOutcomes_ShouldReturnZero() var result = await options.DelayGenerator( new(ResilienceContextPool.Shared.Get(), Outcome.FromResult(responseMessage), - 0, - TimeSpan.Zero)); - Assert.Equal(result, TimeSpan.Zero); + 0)); + + result.Should().BeNull(); result = await options.DelayGenerator( new(ResilienceContextPool.Shared.Get(), Outcome.FromResult(null), - 0, - TimeSpan.Zero)); - Assert.Equal(result, TimeSpan.Zero); + 0)); + result.Should().BeNull(); result = await options.DelayGenerator( new(ResilienceContextPool.Shared.Get(), Outcome.FromException(new InvalidOperationException()), - 0, - TimeSpan.Zero)); - Assert.Equal(result, TimeSpan.Zero); + 0)); + result.Should().BeNull(); } [Fact] @@ -137,8 +135,7 @@ public async Task ShouldRetryAfterHeader_WhenResponseContainsRetryAfterHeader_Sh var result = await options.DelayGenerator!( new(ResilienceContextPool.Shared.Get(), Outcome.FromResult(responseMessage), - 0, - TimeSpan.Zero)); + 0)); Assert.Equal(result, TimeSpan.FromSeconds(10)); } diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.Resilience.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.Resilience.cs index 9eba4b17022..083d753fef5 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.Resilience.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.Resilience.cs @@ -92,7 +92,7 @@ public async Task AddResilienceHandler_EnsureFailureResultContext() .AddStandardResilienceHandler() .Configure(options => { - options.RetryOptions.ShouldHandle = _ => PredicateResult.True; + options.RetryOptions.ShouldHandle = _ => PredicateResult.True(); options.RetryOptions.MaxRetryAttempts = 1; options.RetryOptions.Delay = TimeSpan.Zero; }); diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.Standard.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.Standard.cs index 0dbdc38c0e1..3fc1346d6fc 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.Standard.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpClientBuilderExtensionsTests.Standard.cs @@ -159,8 +159,8 @@ public void AddStandardResilienceHandler_EnsureValidated(bool wholePipeline) { if (wholePipeline) { - options.TotalRequestTimeoutOptions.Timeout = TimeSpan.FromSeconds(2); - options.AttemptTimeoutOptions.Timeout = TimeSpan.FromSeconds(1); + options.TotalRequestTimeoutOptions.Timeout = TimeSpan.FromSeconds(1); + options.AttemptTimeoutOptions.Timeout = TimeSpan.FromSeconds(2); } else { diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpStandardResilienceOptionsCustomValidatorTests.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpStandardResilienceOptionsCustomValidatorTests.cs index 7fd22a7ef5c..b322d6a0e13 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpStandardResilienceOptionsCustomValidatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpStandardResilienceOptionsCustomValidatorTests.cs @@ -32,7 +32,7 @@ public void Validate_InvalidOptions_EnsureValidationErrors() #if NET8_0_OR_GREATER // Whilst these API are marked as NET6_0_OR_GREATER we don't build .NET 6.0, // and as such the API is available in .NET 8 onwards. - Assert.Equal(3, validationResult.Failures.Count()); + Assert.Equal(2, validationResult.Failures.Count()); #endif } @@ -90,10 +90,6 @@ public static IEnumerable GetOptions_InvalidOptions_EnsureErrors_Data options.TotalRequestTimeoutOptions.Timeout = TimeSpan.FromSeconds(2); yield return new object[] { options }; - options = new HttpStandardResilienceOptions(); - options.RetryOptions.Delay = TimeSpan.FromDays(1); - yield return new object[] { options }; - options = new HttpStandardResilienceOptions(); options.AttemptTimeoutOptions.Timeout = options.TotalRequestTimeoutOptions.Timeout; options.CircuitBreakerOptions.SamplingDuration = TimeSpan.FromMilliseconds(options.AttemptTimeoutOptions.Timeout.TotalMilliseconds / 2); From 1b7f8b5abc5a085eb7036bb90ed10439107cda51 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Thu, 31 Aug 2023 14:53:49 +0200 Subject: [PATCH 3/4] Fix warnings --- .../Polly/HttpRetryStrategyOptions.cs | 4 ++-- .../HttpStandardResilienceOptionsCustomValidator.cs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpRetryStrategyOptions.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpRetryStrategyOptions.cs index d2ceaf3b51f..2fd9dc6eec1 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpRetryStrategyOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpRetryStrategyOptions.cs @@ -40,8 +40,8 @@ public HttpRetryStrategyOptions() /// /// /// If the property is set to then the generator will resolve the delay - /// based on the Retry-After header rules, otherwise it will return - /// that was suggested by the retry strategy. + /// based on the Retry-After header rules, otherwise it will return and the retry strategy + /// delay will generate the delay based on the configured options. /// public bool ShouldRetryAfterHeader { diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/Validators/HttpStandardResilienceOptionsCustomValidator.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/Validators/HttpStandardResilienceOptionsCustomValidator.cs index 85a90836449..21b98da09bd 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/Validators/HttpStandardResilienceOptionsCustomValidator.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Resilience/Internal/Validators/HttpStandardResilienceOptionsCustomValidator.cs @@ -3,7 +3,6 @@ using System; using Microsoft.Extensions.Options; -using Polly.Retry; namespace Microsoft.Extensions.Http.Resilience.Internal.Validators; From ad6d2d840b177444c90d9c343b18eba5f4813da2 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Thu, 31 Aug 2023 16:00:19 +0200 Subject: [PATCH 4/4] fix warnings --- .../Internal/RequestMessageSnapshotStrategyTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/RequestMessageSnapshotStrategyTests.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/RequestMessageSnapshotStrategyTests.cs index f1a54374535..06f21c7d9f1 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/RequestMessageSnapshotStrategyTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Internal/RequestMessageSnapshotStrategyTests.cs @@ -31,7 +31,6 @@ public async Task SendAsync_EnsureSnapshotAttached() context); } - [Fact] public void ExecuteAsync_requestMessageNotFound_Throws() {