diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpClientHedgingResiliencePredicates.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpClientHedgingResiliencePredicates.cs index 5955510faab..81358b4801d 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpClientHedgingResiliencePredicates.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpClientHedgingResiliencePredicates.cs @@ -2,7 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; using System.Net.Http; +using System.Threading; +using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; using Polly; using Polly.CircuitBreaker; @@ -17,13 +20,26 @@ public static class HttpClientHedgingResiliencePredicates /// /// Determines whether an outcome should be treated by hedging as a transient failure. /// + /// The outcome of the user-specified callback. /// if outcome is transient, if not. - public static bool IsTransient(Outcome outcome) => outcome switch - { - { Result: { } response } when HttpClientResiliencePredicates.IsTransientHttpFailure(response) => true, - { Exception: { } exception } when IsTransientHttpException(exception) => true, - _ => false, - }; + public static bool IsTransient(Outcome outcome) + => outcome switch + { + { Result: { } response } when HttpClientResiliencePredicates.IsTransientHttpFailure(response) => true, + { Exception: { } exception } when IsTransientHttpException(exception) => true, + _ => false, + }; + + /// + /// Determines whether an should be treated by hedging as a transient failure. + /// + /// The outcome of the user-specified callback. + /// The associated with the execution. + /// if outcome is transient, if not. + [Experimental(diagnosticId: DiagnosticIds.Experiments.Resilience, UrlFormat = DiagnosticIds.UrlFormat)] + public static bool IsTransient(Outcome outcome, CancellationToken cancellationToken) + => HttpClientResiliencePredicates.IsHttpConnectionTimeout(outcome, cancellationToken) + || IsTransient(outcome); /// /// Determines whether an exception should be treated by hedging as a transient failure. @@ -35,8 +51,7 @@ internal static bool IsTransientHttpException(Exception exception) return exception switch { BrokenCircuitException => true, - _ when HttpClientResiliencePredicates.IsTransientHttpException(exception) => true, - _ => false, + _ => HttpClientResiliencePredicates.IsTransientHttpException(exception), }; } } diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpHedgingStrategyOptions.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpHedgingStrategyOptions.cs index faa46375f65..07272cc9916 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpHedgingStrategyOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpHedgingStrategyOptions.cs @@ -16,11 +16,11 @@ public class HttpHedgingStrategyOptions : HedgingStrategyOptions class. /// /// - /// By default the options is set to handle only transient failures, + /// By default, the options is set to handle only transient failures, /// i.e. timeouts, 5xx responses and exceptions. /// public HttpHedgingStrategyOptions() { - ShouldHandle = args => new ValueTask(HttpClientHedgingResiliencePredicates.IsTransient(args.Outcome)); + ShouldHandle = args => new ValueTask(HttpClientHedgingResiliencePredicates.IsTransient(args.Outcome, args.Context.CancellationToken)); } } diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpClientResiliencePredicates.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpClientResiliencePredicates.cs index 0990c779842..3345c75f11a 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpClientResiliencePredicates.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpClientResiliencePredicates.cs @@ -2,8 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Http; +using System.Threading; +using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; using Polly; using Polly.Timeout; @@ -26,6 +29,17 @@ public static class HttpClientResiliencePredicates _ => false }; + /// + /// Determines whether an should be treated by resilience strategies as a transient failure. + /// + /// The outcome of the user-specified callback. + /// The associated with the execution. + /// if outcome is transient, if not. + [Experimental(diagnosticId: DiagnosticIds.Experiments.Resilience, UrlFormat = DiagnosticIds.UrlFormat)] + public static bool IsTransient(Outcome outcome, CancellationToken cancellationToken) + => IsHttpConnectionTimeout(outcome, cancellationToken) + || IsTransient(outcome); + /// /// Determines whether an exception should be treated by resilience strategies as a transient failure. /// @@ -33,10 +47,14 @@ internal static bool IsTransientHttpException(Exception exception) { _ = Throw.IfNull(exception); - return exception is HttpRequestException || - exception is TimeoutRejectedException; + return exception is HttpRequestException or TimeoutRejectedException; } + internal static bool IsHttpConnectionTimeout(in Outcome outcome, in CancellationToken cancellationToken) + => !cancellationToken.IsCancellationRequested + && outcome.Exception is OperationCanceledException { Source: "System.Private.CoreLib" } + && outcome.Exception.InnerException is TimeoutException; + /// /// Determines whether a response contains a transient failure. /// @@ -52,7 +70,6 @@ internal static bool IsTransientHttpFailure(HttpResponseMessage response) return statusCode >= InternalServerErrorCode || response.StatusCode == HttpStatusCode.RequestTimeout || statusCode == TooManyRequests; - } private const int InternalServerErrorCode = (int)HttpStatusCode.InternalServerError; diff --git a/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpRetryStrategyOptions.cs b/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpRetryStrategyOptions.cs index 2153862f8a4..afde7d0372d 100644 --- a/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpRetryStrategyOptions.cs +++ b/src/Libraries/Microsoft.Extensions.Http.Resilience/Polly/HttpRetryStrategyOptions.cs @@ -26,7 +26,7 @@ public class HttpRetryStrategyOptions : RetryStrategyOptions public HttpRetryStrategyOptions() { - ShouldHandle = args => new ValueTask(HttpClientResiliencePredicates.IsTransient(args.Outcome)); + ShouldHandle = args => new ValueTask(HttpClientResiliencePredicates.IsTransient(args.Outcome, args.Context.CancellationToken)); BackoffType = DelayBackoffType.Exponential; ShouldRetryAfterHeader = true; UseJitter = true; 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 f3c132e7c67..6b81d189ee6 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/HedgingTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/HedgingTests.cs @@ -35,6 +35,7 @@ public abstract class HedgingTests : IDisposable private readonly Func _requestRoutingStrategyFactory; private readonly IServiceCollection _services; private readonly Queue _responses = new(); + private ServiceProvider? _serviceProvider; private bool _failure; private protected HedgingTests(Func, TBuilder> createDefaultBuilder) @@ -63,6 +64,11 @@ public void Dispose() _requestRoutingStrategyMock.VerifyAll(); _cancellationTokenSource.Cancel(); _cancellationTokenSource.Dispose(); + _serviceProvider?.Dispose(); + foreach (var response in _responses) + { + response.Dispose(); + } } [Fact] @@ -93,7 +99,7 @@ public async Task SendAsync_EnsureContextFlows() using var client = CreateClientWithHandler(); - await client.SendAsync(request, _cancellationTokenSource.Token); + using var _ = await client.SendAsync(request, _cancellationTokenSource.Token); Assert.Equal(2, calls); } @@ -108,7 +114,7 @@ public async Task SendAsync_NoErrors_ShouldReturnSingleResponse() AddResponse(HttpStatusCode.OK); - var response = await client.SendAsync(request, _cancellationTokenSource.Token); + using var _ = await client.SendAsync(request, _cancellationTokenSource.Token); AssertNoResponse(); Assert.Single(Requests); @@ -164,7 +170,7 @@ public async Task SendAsync_NoRoutesLeftAndSomeResultPresent_ShouldReturn() using var client = CreateClientWithHandler(); - var result = await client.SendAsync(request, _cancellationTokenSource.Token); + using var result = await client.SendAsync(request, _cancellationTokenSource.Token); Assert.Equal(DefaultHedgingAttempts + 1, Requests.Count); Assert.Equal(HttpStatusCode.ServiceUnavailable, result.StatusCode); } @@ -183,7 +189,7 @@ public async Task SendAsync_EnsureDistinctContextForEachAttempt() using var client = CreateClientWithHandler(); - await client.SendAsync(request, _cancellationTokenSource.Token); + using var _ = await client.SendAsync(request, _cancellationTokenSource.Token); RequestContexts.Distinct().OfType().Should().HaveCount(3); } @@ -204,7 +210,7 @@ public async Task SendAsync_EnsureContextReplacedInRequestMessage() using var client = CreateClientWithHandler(); - await client.SendAsync(request, _cancellationTokenSource.Token); + using var _ = await client.SendAsync(request, _cancellationTokenSource.Token); RequestContexts.Distinct().OfType().Should().HaveCount(3); @@ -226,7 +232,7 @@ public async Task SendAsync_NoRoutesLeft_EnsureLessThanMaxHedgedAttempts() using var client = CreateClientWithHandler(); - var result = await client.SendAsync(request, _cancellationTokenSource.Token); + using var _ = await client.SendAsync(request, _cancellationTokenSource.Token); Assert.Equal(2, Requests.Count); } @@ -244,7 +250,7 @@ public async Task SendAsync_FailedExecution_ShouldReturnResponseFromHedging() using var client = CreateClientWithHandler(); - var result = await client.SendAsync(request, _cancellationTokenSource.Token); + using var result = await client.SendAsync(request, _cancellationTokenSource.Token); Assert.Equal(3, Requests.Count); Assert.Equal(HttpStatusCode.OK, result.StatusCode); Assert.Equal("https://enpoint-1:80/some-path?query", Requests[0]); @@ -268,7 +274,12 @@ protected void AddResponse(HttpStatusCode statusCode, int count) protected abstract void ConfigureHedgingOptions(Action configure); - protected HttpClient CreateClientWithHandler() => _services.BuildServiceProvider().GetRequiredService().CreateClient(ClientId); + protected HttpClient CreateClientWithHandler() + { + _serviceProvider?.Dispose(); + _serviceProvider = _services.BuildServiceProvider(); + return _serviceProvider.GetRequiredService().CreateClient(ClientId); + } private Task InnerHandlerFunction(HttpRequestMessage request, CancellationToken cancellationToken) { diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/OperationCanceledExceptionMock.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/OperationCanceledExceptionMock.cs new file mode 100644 index 00000000000..7ea18c5782a --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/OperationCanceledExceptionMock.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Extensions.Http.Resilience.Test.Hedging; + +internal sealed class OperationCanceledExceptionMock : OperationCanceledException +{ + public OperationCanceledExceptionMock(Exception innerException) + : base(null, innerException) + { + } + + public override string? Source { get => "System.Private.CoreLib"; set => base.Source = value; } +} 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 fb381f31e1b..debafe03127 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/StandardHedgingTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Hedging/StandardHedgingTests.cs @@ -46,7 +46,7 @@ public void EnsureValidated_BasicValidation() { Builder.Configure(options => options.Hedging.MaxHedgedAttempts = -1); - Assert.Throws(() => CreateClientWithHandler()); + Assert.Throws(CreateClientWithHandler); } [Fact] @@ -54,7 +54,7 @@ public void EnsureValidated_AdvancedValidation() { Builder.Configure(options => options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(1)); - Assert.Throws(() => CreateClientWithHandler()); + Assert.Throws(CreateClientWithHandler); } [Fact] @@ -62,7 +62,8 @@ public void Configure_Callback_Ok() { Builder.Configure(o => o.Hedging.MaxHedgedAttempts = 8); - var options = Builder.Services.BuildServiceProvider().GetRequiredService>().Get(Builder.Name); + using var serviceProvider = Builder.Services.BuildServiceProvider(); + var options = serviceProvider.GetRequiredService>().Get(Builder.Name); Assert.Equal(8, options.Hedging.MaxHedgedAttempts); } @@ -76,7 +77,8 @@ public void Configure_CallbackWithServiceProvider_Ok() o.Hedging.MaxHedgedAttempts = 8; }); - var options = Builder.Services.BuildServiceProvider().GetRequiredService>().Get(Builder.Name); + using var serviceProvider = Builder.Services.BuildServiceProvider(); + var options = serviceProvider.GetRequiredService>().Get(Builder.Name); Assert.Equal(8, options.Hedging.MaxHedgedAttempts); } @@ -97,7 +99,8 @@ public void Configure_ValidConfigurationSection_ShouldInitialize() Builder.Configure(section); - var options = Builder.Services.BuildServiceProvider().GetRequiredService>().Get(Builder.Name); + using var serviceProvider = Builder.Services.BuildServiceProvider(); + var options = serviceProvider.GetRequiredService>().Get(Builder.Name); Assert.Equal(8, options.Hedging.MaxHedgedAttempts); } @@ -105,7 +108,8 @@ public void Configure_ValidConfigurationSection_ShouldInitialize() [Fact] public void ActionGenerator_Ok() { - var options = Builder.Services.BuildServiceProvider().GetRequiredService>().Get(Builder.Name); + using var serviceProvider = Builder.Services.BuildServiceProvider(); + var options = serviceProvider.GetRequiredService>().Get(Builder.Name); var generator = options.Hedging.ActionGenerator; var primary = ResilienceContextPool.Shared.Get(); var secondary = ResilienceContextPool.Shared.Get(); @@ -133,9 +137,12 @@ public void Configure_InvalidConfigurationSection_ShouldThrow() Builder.Configure(section); Assert.Throws(() => - Builder.Services.BuildServiceProvider() - .GetRequiredService>() - .Get(Builder.Name)); + { + using var serviceProvider = Builder.Services.BuildServiceProvider(); + return serviceProvider + .GetRequiredService>() + .Get(Builder.Name); + }); } #endif @@ -163,7 +170,7 @@ public void Configure_EmptyConfigurationSection_ShouldThrow() [Fact] public void VerifyPipeline() { - var serviceProvider = Builder.Services.BuildServiceProvider(); + using var serviceProvider = Builder.Services.BuildServiceProvider(); var pipelineProvider = serviceProvider.GetRequiredService>(); // primary handler @@ -209,7 +216,7 @@ public async Task VerifyPipelineSelection(string? customKey) using var request = new HttpRequestMessage(HttpMethod.Get, "https://key:80/discarded"); AddResponse(HttpStatusCode.OK); - var response = await client.SendAsync(request, CancellationToken.None); + using var response = await client.SendAsync(request, CancellationToken.None); provider.VerifyAll(); } @@ -235,14 +242,14 @@ public async Task DynamicReloads_Ok() // act && assert AddResponse(HttpStatusCode.InternalServerError, 3); using var firstRequest = new HttpRequestMessage(HttpMethod.Get, "https://to-be-replaced:1234/some-path?query"); - await client.SendAsync(firstRequest); + using var _ = await client.SendAsync(firstRequest); AssertNoResponse(); reloadAction(new() { { "standard:Hedging:MaxHedgedAttempts", "6" } }); AddResponse(HttpStatusCode.InternalServerError, 7); using var secondRequest = new HttpRequestMessage(HttpMethod.Get, "https://to-be-replaced:1234/some-path?query"); - await client.SendAsync(secondRequest); + using var __ = await client.SendAsync(secondRequest); AssertNoResponse(); } @@ -257,11 +264,70 @@ public async Task NoRouting_Ok() // act && assert AddResponse(HttpStatusCode.InternalServerError, 3); using var firstRequest = new HttpRequestMessage(HttpMethod.Get, "https://some-endpoint:1234/some-path?query"); - await client.SendAsync(firstRequest); + using var _ = await client.SendAsync(firstRequest); AssertNoResponse(); Requests.Should().AllSatisfy(r => r.Should().Be("https://some-endpoint:1234/some-path?query")); } + [Fact] + public async Task SendAsync_FailedConnect_ShouldReturnResponseFromHedging() + { + const string FailingEndpoint = "www.failing-host.com"; + + var services = new ServiceCollection(); + _ = services + .AddHttpClient(ClientId) + .ConfigurePrimaryHttpMessageHandler(() => new MockHttpMessageHandler(FailingEndpoint)) + .AddStandardHedgingHandler(routing => + routing.ConfigureOrderedGroups(g => + { + g.Groups.Add(new UriEndpointGroup + { + Endpoints = [new WeightedUriEndpoint { Uri = new Uri($"https://{FailingEndpoint}:3000") }] + }); + + g.Groups.Add(new UriEndpointGroup + { + Endpoints = [new WeightedUriEndpoint { Uri = new Uri("https://microsoft.com") }] + }); + })) + .Configure(opt => + { + opt.Hedging.MaxHedgedAttempts = 10; + opt.Hedging.Delay = TimeSpan.FromSeconds(11); + opt.Endpoint.CircuitBreaker.FailureRatio = 0.99; + opt.Endpoint.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(900); + opt.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(200); + opt.Endpoint.Timeout.Timeout = TimeSpan.FromSeconds(200); + }); + + await using var provider = services.BuildServiceProvider(); + var clientFactory = provider.GetRequiredService(); + using var client = clientFactory.CreateClient(ClientId); + + var ex = await Record.ExceptionAsync(async () => + { + using var _ = await client.GetAsync($"https://{FailingEndpoint}:3000"); + }); + + Assert.Null(ex); + } + protected override void ConfigureHedgingOptions(Action configure) => Builder.Configure(options => configure(options.Hedging)); + + private class MockHttpMessageHandler(string failingEndpoint) : HttpMessageHandler + { + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (request.RequestUri?.Host == failingEndpoint) + { + await Task.Delay(100, cancellationToken); + throw new OperationCanceledExceptionMock(new TimeoutException()); + } + + await Task.Delay(1000, cancellationToken); + return new HttpResponseMessage(HttpStatusCode.OK); + } + } } diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Microsoft.Extensions.Http.Resilience.Tests.csproj b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Microsoft.Extensions.Http.Resilience.Tests.csproj index 266794186c7..95e047fabb3 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Microsoft.Extensions.Http.Resilience.Tests.csproj +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Microsoft.Extensions.Http.Resilience.Tests.csproj @@ -4,11 +4,6 @@ Unit tests for Microsoft.Extensions.Http.Resilience. - - - true - - 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 56fb80c56f9..a9fac7d3e0b 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Polly/HttpRetryStrategyOptionsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Polly/HttpRetryStrategyOptionsTests.cs @@ -6,8 +6,10 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; +using System.Threading; using System.Threading.Tasks; using FluentAssertions; +using Microsoft.Extensions.Http.Resilience.Test.Hedging; using Polly; using Polly.Retry; using Xunit; @@ -19,16 +21,15 @@ public class HttpRetryStrategyOptionsTests #pragma warning disable S2330 public static readonly IEnumerable HandledExceptionsClassified = new[] { - new object[] { new InvalidCastException(), false }, - new object[] { new HttpRequestException(), true } + new object[] { new InvalidCastException(), null!, false }, + [new HttpRequestException(), null!, true], + [new OperationCanceledExceptionMock(new TimeoutException()), null!, true], + [new OperationCanceledExceptionMock(new TimeoutException()), default(CancellationToken), true], + [new OperationCanceledExceptionMock(new InvalidOperationException()), default(CancellationToken), false], + [new OperationCanceledExceptionMock(new TimeoutException()), new CancellationToken(canceled: true), false], }; - private readonly HttpRetryStrategyOptions _testClass; - - public HttpRetryStrategyOptionsTests() - { - _testClass = new HttpRetryStrategyOptions(); - } + private readonly HttpRetryStrategyOptions _testClass = new(); [Fact] public void Ctor_Defaults() @@ -63,9 +64,10 @@ public async Task ShouldHandleResultAsError_DefaultValue_ShouldClassify(HttpStat [Theory] [MemberData(nameof(HandledExceptionsClassified))] - public async Task ShouldHandleException_DefaultValue_ShouldClassify(Exception exception, bool expectedToHandle) + public async Task ShouldHandleException_DefaultValue_ShouldClassify(Exception exception, CancellationToken? token, bool expectedToHandle) { - var shouldHandle = await _testClass.ShouldHandle(CreateArgs(Outcome.FromException(exception))); + var args = CreateArgs(Outcome.FromException(exception), token ?? default); + var shouldHandle = await _testClass.ShouldHandle(args); Assert.Equal(expectedToHandle, shouldHandle); } @@ -86,9 +88,10 @@ public async Task ShouldHandleResultAsError_DefaultInstance_ShouldClassify(HttpS [Theory] [MemberData(nameof(HandledExceptionsClassified))] - public async Task ShouldHandleException_DefaultInstance_ShouldClassify(Exception exception, bool expectedToHandle) + public async Task ShouldHandleException_DefaultInstance_ShouldClassify(Exception exception, CancellationToken? token, bool expectedToHandle) { - var shouldHandle = await new HttpRetryStrategyOptions().ShouldHandle(CreateArgs(Outcome.FromException(exception))); + var args = CreateArgs(Outcome.FromException(exception), token ?? default); + var shouldHandle = await new HttpRetryStrategyOptions().ShouldHandle(args); Assert.Equal(expectedToHandle, shouldHandle); } @@ -96,7 +99,7 @@ public async Task ShouldHandleException_DefaultInstance_ShouldClassify(Exception public async Task ShouldRetryAfterHeader_InvalidOutcomes_ShouldReturnNull() { var options = new HttpRetryStrategyOptions { ShouldRetryAfterHeader = true }; - using var responseMessage = new HttpResponseMessage { }; + using var responseMessage = new HttpResponseMessage(); Assert.NotNull(options.DelayGenerator); @@ -153,7 +156,8 @@ public void GetDelayGenerator_ShouldGetBasedOnShouldRetryAfterHeader(bool should Assert.Equal(shouldRetryAfterHeader, options.DelayGenerator != null); } - private static RetryPredicateArguments CreateArgs(Outcome outcome) - => new(ResilienceContextPool.Shared.Get(), outcome, 0); - + private static RetryPredicateArguments CreateArgs( + Outcome outcome, + CancellationToken cancellationToken = default) + => new(ResilienceContextPool.Shared.Get(cancellationToken), outcome, 0); } 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 afa4233c797..7f0143ded57 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 @@ -128,7 +128,7 @@ public void AddStandardResilienceHandler_ConfigurationPropertyWithTypo_Throws(Me AddStandardResilienceHandler(mode, builder, _invalidConfigurationSection, options => { }); - Assert.Throws(() => HttpClientBuilderExtensionsTests.GetPipeline(builder.Services, $"test-standard")); + Assert.Throws(() => HttpClientBuilderExtensionsTests.GetPipeline(builder.Services, "test-standard")); } [Fact] @@ -175,7 +175,7 @@ public void AddStandardResilienceHandler_EnsureValidated(bool wholePipeline) } }); - Assert.Throws(() => GetPipeline(builder.Services, $"test-standard")); + Assert.Throws(() => GetPipeline(builder.Services, "test-standard")); } [InlineData(MethodArgs.None)] @@ -193,7 +193,7 @@ public void AddStandardResilienceHandler_EnsureConfigured(MethodArgs mode) AddStandardResilienceHandler(mode, builder, _validConfigurationSection, options => { }); - var pipeline = GetPipeline(builder.Services, $"test-standard"); + var pipeline = GetPipeline(builder.Services, "test-standard"); Assert.NotNull(pipeline); } diff --git a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpStandardResilienceOptionsTests.cs b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpStandardResilienceOptionsTests.cs index 5a216aac6ec..9d55b1f8c0e 100644 --- a/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpStandardResilienceOptionsTests.cs +++ b/test/Libraries/Microsoft.Extensions.Http.Resilience.Tests/Resilience/HttpStandardResilienceOptionsTests.cs @@ -9,12 +9,7 @@ namespace Microsoft.Extensions.Http.Resilience.Test.Resilience; public class HttpStandardResilienceOptionsTests { - private readonly HttpStandardResilienceOptions _options; - - public HttpStandardResilienceOptionsTests() - { - _options = new HttpStandardResilienceOptions(); - } + private readonly HttpStandardResilienceOptions _options = new(); [Fact] public void Ctor_EnsureDefaults()