Skip to content

Commit 5212ce0

Browse files
committed
Adopt Polly V8 in Microsoft.Extensions.Http.Resilience
1 parent 04608fd commit 5212ce0

File tree

131 files changed

+2383
-2683
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

131 files changed

+2383
-2683
lines changed

bench/Libraries/Microsoft.Extensions.Http.Resilience.PerformanceTests/HttpClientFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public static ServiceProvider InitializeServiceProvider(HedgingClientType client
6161
private static IServiceCollection AddHedging(this IServiceCollection services, HedgingClientType clientType)
6262
{
6363
var clientBuilder = services.AddHttpClient(clientType.ToString(), client => client.Timeout = Timeout.InfiniteTimeSpan);
64-
var hedgingBuilder = clientBuilder.AddStandardHedgingHandler().SelectPipelineByAuthority(SimpleClassifications.PublicData);
64+
var hedgingBuilder = clientBuilder.AddStandardHedgingHandler().SelectStrategyByAuthority(SimpleClassifications.PublicData);
6565
_ = clientBuilder.AddHttpMessageHandler<NoRemoteCallHandler>();
6666

6767
int routes = clientType.HasFlag(HedgingClientType.ManyRoutes) ? 50 : 2;

eng/Packages/General.props

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@
3434
<PackageVersion Include="OpenTelemetry" Version="1.4.0" />
3535
<PackageVersion Include="Polly.Contrib.Simmy" Version="0.3.0" />
3636
<PackageVersion Include="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
37-
<PackageVersion Include="Polly" Version="8.0.0-alpha.1" />
38-
<PackageVersion Include="Polly.Core" Version="8.0.0-alpha.1" />
39-
<PackageVersion Include="Polly.Extensions" Version="8.0.0-alpha.1" />
40-
<PackageVersion Include="Polly.RateLimiting" Version="8.0.0-alpha.1" />
37+
<PackageVersion Include="Polly" Version="8.0.0-alpha.3" />
38+
<PackageVersion Include="Polly.Core" Version="8.0.0-alpha.3" />
39+
<PackageVersion Include="Polly.Extensions" Version="8.0.0-alpha.3" />
40+
<PackageVersion Include="Polly.RateLimiting" Version="8.0.0-alpha.3" />
4141
<PackageVersion Include="protobuf-net" Version="3.0.101" />
4242
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
4343
<PackageVersion Include="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta4.22272.1" />

src/Libraries/Microsoft.Extensions.Http.Resilience/FaultInjection/HttpClientFaultInjectionExtensions.cs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
using Microsoft.Extensions.Http.Resilience.FaultInjection.Internal;
1111
using Microsoft.Extensions.Options;
1212
using Microsoft.Extensions.Resilience.FaultInjection;
13-
using Microsoft.Extensions.Resilience.Internal;
1413
using Microsoft.Shared.Diagnostics;
14+
using Polly;
1515

1616
namespace Microsoft.Extensions.Http.Resilience.FaultInjection;
1717

@@ -205,18 +205,17 @@ public static IHttpClientBuilder AddWeightedFaultInjectionPolicyHandlers(this IH
205205

206206
private static IHttpClientBuilder AddChaosMessageHandler(this IHttpClientBuilder httpClientBuilder)
207207
{
208-
_ = httpClientBuilder
209-
.AddResilienceHandler("chaos")
210-
.AddPolicy((pipelineBuilder, services) =>
211-
{
212-
var chaosPolicyFactory = services.GetRequiredService<IChaosPolicyFactory>();
213-
var httpClientChaosPolicyFactory = services.GetRequiredService<IHttpClientChaosPolicyFactory>();
214-
_ = pipelineBuilder
215-
.AddPolicy(httpClientChaosPolicyFactory.CreateHttpResponsePolicy())
216-
.AddPolicy(chaosPolicyFactory.CreateExceptionPolicy())
217-
.AddPolicy(chaosPolicyFactory.CreateLatencyPolicy<HttpResponseMessage>());
218-
});
208+
return httpClientBuilder.AddHttpMessageHandler(serviceProvider =>
209+
{
210+
var chaosPolicyFactory = serviceProvider.GetRequiredService<IChaosPolicyFactory>();
211+
var httpClientChaosPolicyFactory = serviceProvider.GetRequiredService<IHttpClientChaosPolicyFactory>();
219212

220-
return httpClientBuilder;
213+
var policy = Policy.WrapAsync(
214+
chaosPolicyFactory.CreateLatencyPolicy<HttpResponseMessage>(),
215+
chaosPolicyFactory.CreateExceptionPolicy().AsAsyncPolicy<HttpResponseMessage>(),
216+
httpClientChaosPolicyFactory.CreateHttpResponsePolicy());
217+
218+
return new PolicyHttpMessageHandler(policy);
219+
});
221220
}
222221
}

src/Libraries/Microsoft.Extensions.Http.Resilience/FaultInjection/PolicyContextExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
namespace Microsoft.Extensions.Http.Resilience.FaultInjection;
1010

1111
/// <summary>
12-
/// Provides extension methods for <see cref="Polly.Context"/>.
12+
/// Provides extension methods for <see cref="Context"/>.
1313
/// </summary>
1414
[Experimental]
1515
public static class PolicyContextExtensions

src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HedgingEndpointOptions.cs

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,50 +3,58 @@
33

44
using System;
55
using System.ComponentModel.DataAnnotations;
6+
using Microsoft.Extensions.Http.Resilience.Internal.Validators;
67
using Microsoft.Extensions.Options.Validation;
7-
using Microsoft.Extensions.Resilience.Options;
8+
using Polly.Timeout;
89

910
namespace Microsoft.Extensions.Http.Resilience;
1011

1112
/// <summary>
12-
/// Options for resilient pipeline of policies assigned to a particular endpoint. It is using three chained layers in this order (from the outermost to the innermost):
13-
/// Bulkhead -> Circuit Breaker -> Attempt Timeout.
13+
/// Options for the pipeline of resilience strategies assigned to a particular endpoint.
1414
/// </summary>
15+
/// <remarks>
16+
/// It is using three chained layers in this order (from the outermost to the innermost): Bulkhead -> Circuit Breaker -> Attempt Timeout.
17+
/// </remarks>
1518
public class HedgingEndpointOptions
1619
{
17-
private static readonly TimeSpan _timeoutInterval = TimeSpan.FromSeconds(10);
18-
1920
/// <summary>
2021
/// Gets or sets the bulkhead options for the endpoint.
2122
/// </summary>
2223
/// <remarks>
23-
/// By default it is initialized with a unique instance of <see cref="HttpBulkheadPolicyOptions"/> using default properties values.
24+
/// By default it is initialized with a unique instance of <see cref="HttpRateLimiterStrategyOptions"/> using default properties values.
2425
/// </remarks>
2526
[Required]
2627
[ValidateObjectMembers]
27-
public HttpBulkheadPolicyOptions BulkheadOptions { get; set; } = new();
28+
public HttpRateLimiterStrategyOptions RateLimiterOptions { get; set; } = new HttpRateLimiterStrategyOptions
29+
{
30+
StrategyName = StandardHedgingStrategyNames.RateLimiter
31+
};
2832

2933
/// <summary>
3034
/// Gets or sets the circuit breaker options for the endpoint.
3135
/// </summary>
3236
/// <remarks>
33-
/// By default it is initialized with a unique instance of <see cref="HttpCircuitBreakerPolicyOptions"/> using default properties values.
37+
/// By default it is initialized with a unique instance of <see cref="HttpCircuitBreakerStrategyOptions"/> using default properties values.
3438
/// </remarks>
3539
[Required]
3640
[ValidateObjectMembers]
37-
public HttpCircuitBreakerPolicyOptions CircuitBreakerOptions { get; set; } = new();
41+
public HttpCircuitBreakerStrategyOptions CircuitBreakerOptions { get; set; } = new HttpCircuitBreakerStrategyOptions
42+
{
43+
StrategyName = StandardHedgingStrategyNames.CircuitBreaker
44+
};
3845

3946
/// <summary>
40-
/// Gets or sets the options for the timeout policy applied per each request attempt.
47+
/// Gets or sets the options for the timeout resilience strategy applied per each request attempt.
4148
/// </summary>
4249
/// <remarks>
43-
/// By default it is initialized with a unique instance of <see cref="HttpTimeoutPolicyOptions"/>
44-
/// using a custom <see cref="TimeoutPolicyOptions.TimeoutInterval"/> of 10 seconds.
50+
/// By default it is initialized with a unique instance of <see cref="HttpTimeoutStrategyOptions"/>
51+
/// using a custom <see cref="TimeoutStrategyOptions.Timeout"/> of 10 seconds.
4552
/// </remarks>
4653
[Required]
4754
[ValidateObjectMembers]
48-
public HttpTimeoutPolicyOptions TimeoutOptions { get; set; } = new()
55+
public HttpTimeoutStrategyOptions TimeoutOptions { get; set; } = new()
4956
{
50-
TimeoutInterval = _timeoutInterval,
57+
Timeout = TimeSpan.FromSeconds(10),
58+
StrategyName = StandardHedgingStrategyNames.AttemptTimeout
5159
};
5260
}

src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpClientBuilderExtensions.Hedging.cs

Lines changed: 82 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,24 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5-
using System.Net.Http;
65
using Microsoft.Extensions.Compliance.Classification;
76
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.Extensions.DependencyInjection.Extensions;
88
using Microsoft.Extensions.Http.Resilience.Internal;
9-
using Microsoft.Extensions.Http.Resilience.Internal.Routing;
109
using Microsoft.Extensions.Http.Resilience.Internal.Validators;
1110
using Microsoft.Extensions.Http.Resilience.Routing.Internal;
12-
using Microsoft.Extensions.Resilience.Internal;
11+
using Microsoft.Extensions.Options;
12+
using Microsoft.Extensions.Options.Validation;
1313
using Microsoft.Shared.Diagnostics;
14+
using Polly;
1415

1516
namespace Microsoft.Extensions.Http.Resilience;
1617

1718
public static partial class HttpClientBuilderExtensions
1819
{
20+
internal const string StandardInnerHandlerPostfix = "standard-hedging-endpoint";
21+
1922
private const string StandardHandlerPostfix = "standard-hedging";
20-
private const string StandardInnerHandlerPostfix = "standard-hedging-endpoint";
2123

2224
/// <summary>
2325
/// Adds a standard hedging handler which wraps the execution of the request with a standard hedging mechanism.
@@ -28,12 +30,14 @@ public static partial class HttpClientBuilderExtensions
2830
/// A <see cref="IStandardHedgingHandlerBuilder"/> builder that can be used to configure the standard hedging behavior.
2931
/// </returns>
3032
/// <remarks>
31-
/// The standard hedging uses a pipeline pool of circuit breakers to ensure that unhealthy endpoints are not hedged against.
33+
/// The standard hedging uses a pool of circuit breakers to ensure that unhealthy endpoints are not hedged against.
3234
/// By default, the selection from pool is based on the URL Authority (scheme + host + port).
33-
///
34-
/// It is recommended that you configure the way the pipelines are selected by calling 'SelectPipelineByAuthority' extensions on top of returned <see cref="IStandardHedgingHandlerBuilder"/>.
35-
///
36-
/// See <see cref="HttpStandardHedgingResilienceOptions"/> for more details about the policies inside the pipeline.
35+
/// It is recommended that you configure the way the strategies are selected by calling
36+
/// <see cref="StandardHedgingHandlerBuilderExtensions.SelectStrategyByAuthority(IStandardHedgingHandlerBuilder, DataClassification)"/>
37+
/// extensions.
38+
/// <para>
39+
/// See <see cref="HttpStandardHedgingResilienceOptions"/> for more details about the used resilience strategies.
40+
/// </para>
3741
/// </remarks>
3842
public static IStandardHedgingHandlerBuilder AddStandardHedgingHandler(this IHttpClientBuilder builder, Action<IRoutingStrategyBuilder> configure)
3943
{
@@ -55,45 +59,86 @@ public static IStandardHedgingHandlerBuilder AddStandardHedgingHandler(this IHtt
5559
/// A <see cref="IStandardHedgingHandlerBuilder"/> builder that can be used to configure the standard hedging behavior.
5660
/// </returns>
5761
/// <remarks>
58-
/// The standard hedging uses a pipeline pool of circuit breakers to ensure that unhealthy endpoints are not hedged against.
62+
/// The standard hedging uses a pool of circuit breakers to ensure that unhealthy endpoints are not hedged against.
5963
/// By default, the selection from pool is based on the URL Authority (scheme + host + port).
60-
///
61-
/// It is recommended that you configure the way the pipelines are selected by calling 'SelectPipelineByAuthority' extensions on top of returned <see cref="IStandardHedgingHandlerBuilder"/>.
62-
///
63-
/// See <see cref="HttpStandardHedgingResilienceOptions"/> for more details about the policies inside the pipeline.
64+
/// It is recommended that you configure the way the strategies are selected by calling
65+
/// <see cref="StandardHedgingHandlerBuilderExtensions.SelectStrategyByAuthority(IStandardHedgingHandlerBuilder, DataClassification)"/>
66+
/// extensions.
67+
/// <para>
68+
/// See <see cref="HttpStandardHedgingResilienceOptions"/> for more details about the used resilience strategies.
69+
/// </para>
6470
/// </remarks>
6571
public static IStandardHedgingHandlerBuilder AddStandardHedgingHandler(this IHttpClientBuilder builder)
6672
{
6773
_ = Throw.IfNull(builder);
6874

6975
var optionsName = builder.Name;
7076
var routingBuilder = new RoutingStrategyBuilder(builder.Name, builder.Services);
71-
_ = builder.Services.AddRequestCloner();
77+
builder.Services.TryAddSingleton<IRequestCloner, RequestCloner>();
78+
_ = builder.Services.AddValidatedOptions<HttpStandardHedgingResilienceOptions, HttpStandardHedgingResilienceOptionsValidator>(optionsName);
79+
_ = builder.Services.AddValidatedOptions<HttpStandardHedgingResilienceOptions, HttpStandardHedgingResilienceOptionsCustomValidator>(optionsName);
80+
_ = builder.Services.PostConfigure<HttpStandardHedgingResilienceOptions>(optionsName, options =>
81+
{
82+
options.HedgingOptions.HedgingActionGenerator = args =>
83+
{
84+
if (!args.PrimaryContext.Properties.TryGetValue(ResilienceKeys.RequestSnapshot, out var snapshot))
85+
{
86+
Throw.InvalidOperationException("Request message snapshot is not attached to the resilience context.");
87+
}
88+
89+
if (!args.PrimaryContext.Properties.TryGetValue(ResilienceKeys.RoutingStrategy, out var routingStrategy))
90+
{
91+
Throw.InvalidOperationException("Routing strategy is not attached to the resilience context.");
92+
}
93+
94+
if (!routingStrategy.TryGetNextRoute(out var route))
95+
{
96+
// no routes left, stop hedging
97+
return null;
98+
}
99+
100+
var requestMessage = snapshot.Create().ReplaceHost(route);
101+
102+
// replace the request message
103+
args.ActionContext.Properties.Set(ResilienceKeys.RequestMessage, requestMessage);
104+
105+
return () => args.Callback(args.ActionContext);
106+
};
107+
});
72108

73109
// configure outer handler
74-
var outerHandler = builder.AddResilienceHandler(StandardHandlerPostfix);
75-
_ = outerHandler
76-
.AddRoutingPolicy(serviceProvider => serviceProvider.GetRoutingFactory(routingBuilder.Name))
77-
.AddRequestMessageSnapshotPolicy()
78-
.AddPolicy<HttpResponseMessage, HttpStandardHedgingResilienceOptions, HttpStandardHedgingResilienceOptionsCustomValidator>(
79-
optionsName,
80-
options => { },
81-
(builder, options, _) => builder
82-
.AddTimeoutPolicy(StandardHedgingPolicyNames.TotalRequestTimeout, options.TotalRequestTimeoutOptions)
83-
.AddHedgingPolicy(StandardHedgingPolicyNames.Hedging, CreateHedgedTaskProvider(outerHandler.PipelineName), options.HedgingOptions));
110+
var outerHandler = builder.AddResilienceHandler(StandardHandlerPostfix, (builder, context) =>
111+
{
112+
var options = context.GetOptions<HttpStandardHedgingResilienceOptions>(optionsName);
113+
context.EnableReloads<HttpStandardHedgingResilienceOptions>(optionsName);
114+
115+
_ = builder
116+
.AddStrategy(new RoutingStrategy(context.ServiceProvider.GetRoutingFactory(routingBuilder.Name)))
117+
.AddStrategy(new RequestMessageSnapshotStrategy(context.ServiceProvider.GetRequiredService<IRequestCloner>()))
118+
.AddTimeout(options.TotalRequestTimeoutOptions)
119+
.AddHedging(options.HedgingOptions);
120+
});
84121

85122
// configure inner handler
86-
var innerBuilder = builder.AddResilienceHandler(StandardInnerHandlerPostfix);
87-
_ = innerBuilder
88-
.SelectPipelineByAuthority(new DataClassification("FIXME", 1))
89-
.AddPolicy<HttpResponseMessage, HttpStandardHedgingResilienceOptions, HttpStandardHedgingResilienceOptionsValidator>(
90-
optionsName,
91-
options => { },
92-
(builder, options, _) => builder
93-
.AddBulkheadPolicy(StandardHedgingPolicyNames.Bulkhead, options.EndpointOptions.BulkheadOptions)
94-
.AddCircuitBreakerPolicy(StandardHedgingPolicyNames.CircuitBreaker, options.EndpointOptions.CircuitBreakerOptions)
95-
.AddTimeoutPolicy(StandardHedgingPolicyNames.AttemptTimeout, options.EndpointOptions.TimeoutOptions));
96-
97-
return new StandardHedgingHandlerBuilder(builder.Name, builder.Services, routingBuilder, innerBuilder);
123+
var innerBuilder = builder.AddResilienceHandler(
124+
StandardInnerHandlerPostfix,
125+
(builder, context) =>
126+
{
127+
var options = context.GetOptions<HttpStandardHedgingResilienceOptions>(optionsName);
128+
context.EnableReloads<HttpStandardHedgingResilienceOptions>(optionsName);
129+
130+
_ = builder
131+
.AddRateLimiter(options.EndpointOptions.RateLimiterOptions)
132+
.AddAdvancedCircuitBreaker(options.EndpointOptions.CircuitBreakerOptions)
133+
.AddTimeout(options.EndpointOptions.TimeoutOptions);
134+
})
135+
.SelectStrategyByAuthority(DataClassification.Unknown);
136+
137+
return new StandardHedgingHandlerBuilder(builder.Name, builder.Services, routingBuilder);
98138
}
139+
140+
private record StandardHedgingHandlerBuilder(
141+
string Name,
142+
IServiceCollection Services,
143+
IRoutingStrategyBuilder RoutingStrategyBuilder) : IStandardHedgingHandlerBuilder;
99144
}

src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpClientBuilderExtensions.cs

Lines changed: 0 additions & 42 deletions
This file was deleted.

src/Libraries/Microsoft.Extensions.Http.Resilience/Hedging/HttpClientHedgingResiliencePredicates.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.Net.Http;
56
using Microsoft.Shared.Diagnostics;
7+
using Polly;
68
using Polly.CircuitBreaker;
79

810
namespace Microsoft.Extensions.Http.Resilience;
@@ -26,4 +28,14 @@ _ when HttpClientResiliencePredicates.IsTransientHttpException(exception) => tru
2628
_ => false,
2729
};
2830
};
31+
32+
/// <summary>
33+
/// Determines whether an outcome should be treated by hedging as a transient failure.
34+
/// </summary>
35+
public static readonly Predicate<Outcome<HttpResponseMessage>> IsTransientHttpOutcome = outcome => outcome switch
36+
{
37+
{ Result: { } response } when HttpClientResiliencePredicates.IsTransientHttpFailure(response) => true,
38+
{ Exception: { } exception } when IsTransientHttpException(exception) => true,
39+
_ => false,
40+
};
2941
}

0 commit comments

Comments
 (0)