-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
1,700 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
# General | ||
|
||
## Supported targets | ||
|
||
Polly targets .NET Standard 2.0+ ([coverage](https://docs.microsoft.com/en-us/dotnet/standard/net-standard#net-implementation-support): .NET Core 2.0+, .NET Core 3.0, and later Mono, Xamarin and UWP targets). The NuGet package also includes direct targets for .NET Framework 4.6.1 and 4.7.2. | ||
|
||
For details of supported compilation targets by version, see the [supported targets](https://github.com/App-vNext/Polly/wiki/Supported-targets) grid. | ||
|
||
## Policy Keys and Context data | ||
|
||
```csharp | ||
// Identify policies with a PolicyKey, using the WithPolicyKey() extension method | ||
// (for example, for correlation in logs or metrics) | ||
var policy = Policy | ||
.Handle<DataAccessException>() | ||
.Retry(3, onRetry: (exception, retryCount, context) => | ||
{ | ||
logger.Error($"Retry {retryCount} of {context.PolicyKey} at {context.OperationKey}, due to: {exception}."); | ||
}) | ||
.WithPolicyKey("MyDataAccessPolicy"); | ||
|
||
// Identify call sites with an OperationKey, by passing in a Context | ||
var customerDetails = policy.Execute(myDelegate, new Context("GetCustomerDetails")); | ||
|
||
// "MyDataAccessPolicy" -> context.PolicyKey | ||
// "GetCustomerDetails -> context.OperationKey | ||
|
||
// Pass additional custom information from call site into execution context | ||
var policy = Policy | ||
.Handle<DataAccessException>() | ||
.Retry(3, onRetry: (exception, retryCount, context) => | ||
{ | ||
logger.Error($"Retry {retryCount} of {context.PolicyKey} at {context.OperationKey}, getting {context["Type"]} of id {context["Id"]}, due to: {exception}."); | ||
}) | ||
.WithPolicyKey("MyDataAccessPolicy"); | ||
|
||
int id = ... // customer id from somewhere | ||
var customerDetails = policy.Execute(context => GetCustomer(id), | ||
new Context("GetCustomerDetails", new Dictionary<string, object>() {{"Type","Customer"},{"Id",id}} | ||
)); | ||
``` | ||
|
||
For more detail see: [Keys and Context Data](https://github.com/App-vNext/Polly/wiki/Keys-And-Context-Data) on wiki. | ||
|
||
## PolicyRegistry | ||
|
||
```csharp | ||
// Create a policy registry (for example on application start-up) | ||
PolicyRegistry registry = new PolicyRegistry(); | ||
|
||
// Populate the registry with policies | ||
registry.Add("StandardHttpResilience", myStandardHttpResiliencePolicy); | ||
// Or: | ||
registry["StandardHttpResilience"] = myStandardHttpResiliencePolicy; | ||
|
||
// Pass the registry instance to usage sites by DI, perhaps | ||
public class MyServiceGateway | ||
{ | ||
public void MyServiceGateway(..., IReadOnlyPolicyRegistry<string> registry, ...) | ||
{ | ||
... | ||
} | ||
} | ||
// (Or if you prefer ambient-context pattern, use a thread-safe singleton) | ||
// Use a policy from the registry | ||
registry.Get<IAsyncPolicy<HttpResponseMessage>>("StandardHttpResilience") | ||
.ExecuteAsync<HttpResponseMessage>(...) | ||
``` | ||
|
||
`PolicyRegistry` has a range of further dictionary-like semantics such as `.ContainsKey(...)`, `.TryGet<TPolicy>(...)`, `.Count`, `.Clear()`, and `Remove(...)`. | ||
|
||
Available from v5.2.0. For more detail see: [PolicyRegistry](https://github.com/App-vNext/Polly/wiki/PolicyRegistry) on wiki. | ||
|
||
## Asynchronous support | ||
|
||
Polly provides native support for asynchronous operations through all its resilience strategies by offering the `ExecuteAsync` methods on the `ResiliencePipeline` class. | ||
|
||
### SynchronizationContext | ||
|
||
By default, asynchronous continuations and retries do not execute on a captured synchronization context. To modify this behavior, you can use the `ResilienceContext` class and set its `ContinueOnCapturedContext` property to `true`. The following example illustrates this: | ||
|
||
<!-- snippet: synchronization-context --> | ||
```cs | ||
// Retrieve an instance of ResilienceContext from the pool | ||
// with the ContinueOnCapturedContext property set to true | ||
ResilienceContext context = ResilienceContextPool.Shared.Get(continueOnCapturedContext: true); | ||
|
||
await pipeline.ExecuteAsync( | ||
async context => | ||
{ | ||
// Execute your code, honoring the ContinueOnCapturedContext setting | ||
await MyMethodAsync(context.CancellationToken).ConfigureAwait(context.ContinueOnCapturedContext); | ||
}, | ||
context); | ||
|
||
// Optionally, return the ResilienceContext instance back to the pool | ||
// to minimize allocations and enhance performance | ||
ResilienceContextPool.Shared.Return(context); | ||
``` | ||
<!-- endSnippet --> | ||
|
||
### Cancellation support | ||
|
||
Asynchronous pipeline execution in Polly supports cancellation. This is facilitated through the `ExecuteAsync(...)` method overloads that accept a `CancellationToken`, or by initializing the `ResilienceContext` class with the `CancellationToken` property. | ||
|
||
The `CancellationToken` you pass to the `ExecuteAsync(...)` method serves multiple functions: | ||
|
||
- It cancels resilience actions such as retries, wait times between retries, or rate-limiter leases. | ||
- It is passed to any delegate executed by the strategy as a `CancellationToken` parameter, enabling cancellation during the delegate's execution. | ||
- Consistent with the .NET Base Class Library's behavior in `Task.Run(...)`, if the cancellation token is cancelled before execution begins, the user-defined delegate will not execute at all. | ||
|
||
<!-- snippet: cancellation-token --> | ||
```cs | ||
// Execute your code with cancellation support | ||
await pipeline.ExecuteAsync( | ||
async token => await MyMethodAsync(token), | ||
cancellationToken); | ||
|
||
// Use ResilienceContext for more advanced scenarios | ||
ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken: cancellationToken); | ||
|
||
await pipeline.ExecuteAsync( | ||
async context => await MyMethodAsync(context.CancellationToken), | ||
context); | ||
``` | ||
<!-- endSnippet --> | ||
|
||
## Thread safety | ||
|
||
All Polly resilience strategies are fully thread-safe. You can safely re-use strategies at multiple call sites, and execute through strategies concurrently on different threads. | ||
|
||
> [!IMPORTANT] | ||
> While the internal operation of the strategy is thread-safe, this does not magically make delegates you execute through the strategy thread-safe: if delegates you execute through the strategy are not thread-safe, they remain not thread-safe. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
# Resilience pipelines | ||
|
||
## Usage | ||
|
||
The `ResiliencePipeline` allow executing various synchronous and asynchronous user-provided callbacks as seen in the examples bellow: | ||
|
||
<!-- snippet: resilience-pipeline-usage --> | ||
```cs | ||
// Creating a new resilience pipeline | ||
ResiliencePipeline pipeline = new ResiliencePipelineBuilder() | ||
.AddConcurrencyLimiter(100) | ||
.Build(); | ||
|
||
// Executing an asynchronous void callback | ||
await pipeline.ExecuteAsync( | ||
async token => await MyMethodAsync(token), | ||
cancellationToken); | ||
|
||
// Executing a synchronous void callback | ||
pipeline.Execute(() => MyMethod()); | ||
|
||
// Executing an asynchronous callback that returns a value | ||
await pipeline.ExecuteAsync( | ||
async token => await httpClient.GetAsync(endpoint, token), | ||
cancellationToken); | ||
|
||
// Executing an asynchronous callback without allocating a lambda | ||
await pipeline.ExecuteAsync( | ||
static async (state, token) => await state.httpClient.GetAsync(state.endpoint, token), | ||
(httpClient, endpoint), // State provided here | ||
cancellationToken); | ||
|
||
// Executing an asynchronous callback and passing custom data | ||
// 1. Retrieve a context from the shared pool | ||
ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken); | ||
|
||
// 2. Add custom data to the context | ||
context.Properties.Set(new ResiliencePropertyKey<string>("my-custom-data"), "my-custom-data"); | ||
|
||
// 3. Execute the callback | ||
await pipeline.ExecuteAsync(static async context => | ||
{ | ||
// Retrieve custom data from the context | ||
var customData = context.Properties.GetValue( | ||
new ResiliencePropertyKey<string>("my-custom-data"), | ||
"default-value"); | ||
|
||
Console.WriteLine("Custom Data: {0}", customData); | ||
|
||
await MyMethodAsync(context.CancellationToken); | ||
}, | ||
context); | ||
|
||
// 4. Optionally, return the context to the shared pool | ||
ResilienceContextPool.Shared.Return(context); | ||
``` | ||
<!-- endSnippet --> | ||
|
||
The above samples demonstrate how to use the resilience pipeline within the same scope. Additionally, consider the following: | ||
|
||
- Separate the resilience pipeline's definition from its usage. Inject pipelines into the code that will consume them. This [facilitates various unit-testing scenarios](https://github.com/App-vNext/Polly/wiki/Unit-testing-with-Polly---with-examples). | ||
- If your application uses Polly in multiple locations, define all pipelines at startup using [`ResiliencePipelineRegistry`](/docs/registry.md-TODO) or using the `AddResiliencePipeline` extension. This is a common approach in .NET Core applications. For example, you could create your own extension method on `IServiceCollection` to configure pipelines consumed elsewhere in your application. | ||
|
||
<!-- snippet: resilience-pipeline-di-usage --> | ||
```cs | ||
public static void ConfigureMyPipelines(IServiceCollection services) | ||
{ | ||
services.AddResiliencePipeline("pipeline-A", builder => builder.AddConcurrencyLimiter(100)); | ||
services.AddResiliencePipeline("pipeline-B", builder => builder.AddRetry(new())); | ||
|
||
// Later, resolve the pipeline by name using ResiliencePipelineProvider<string> or ResiliencePipelineRegistry<string> | ||
var pipelineProvider = services.BuildServiceProvider().GetRequiredService<ResiliencePipelineProvider<string>>(); | ||
pipelineProvider.GetPipeline("pipeline-A").Execute(() => { }); | ||
} | ||
``` | ||
<!-- endSnippet --> | ||
|
||
> [!NOTE] | ||
> In certain scenarios, such as testing, you may want to use the ResiliencePipeline.Empty instance. This instance executes the callback without applying any additional logic. | ||
## Retrieving execution results with `Outcome<T>` | ||
|
||
The `ResiliencePipeline` class provides the `ExecuteOutcomeAsync` method, which is designed to never throw exceptions. Instead, it stores either the result or the exception within an `Outcome<T>` struct. | ||
|
||
<!-- snippet: resilience-pipeline-outcome --> | ||
```cs | ||
// Acquire a ResilienceContext from the pool | ||
ResilienceContext context = ResilienceContextPool.Shared.Get(); | ||
|
||
// Execute the pipeline and store the result in an Outcome<bool> | ||
Outcome<bool> outcome = await pipeline.ExecuteOutcomeAsync( | ||
static async (context, state) => | ||
{ | ||
Console.WriteLine("State: {0}", state); | ||
|
||
try | ||
{ | ||
await MyMethodAsync(context.CancellationToken); | ||
|
||
// Use static utility methods from Outcome to easily create an Outcome<T> instance | ||
return Outcome.FromResult(true); | ||
} | ||
catch (Exception e) | ||
{ | ||
// Create an Outcome<T> instance that holds the exception | ||
return Outcome.FromException<bool>(e); | ||
} | ||
}, | ||
context, | ||
"my-state"); | ||
|
||
// Return the acquired ResilienceContext to the pool | ||
ResilienceContextPool.Shared.Return(context); | ||
|
||
// Evaluate the outcome | ||
if (outcome.Exception is not null) | ||
{ | ||
Console.WriteLine("Execution Failed: {0}", outcome.Exception.Message); | ||
} | ||
else | ||
{ | ||
Console.WriteLine("Execution Result: {0}", outcome.Result); | ||
} | ||
``` | ||
<!-- endSnippet --> | ||
|
||
Utilize `ExecuteOutcomeAsync` in high-performance scenarios where you wish to avoid re-throwing exceptions. Keep in mind that Polly's resilience strategies also make use of the `Outcome` struct to prevent unnecessary exception throwing. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# Resilience Strategies | ||
|
||
## Usage | ||
|
||
Extensions for adding resilience strategies to the builders are provided by each strategy. Depending on the type of strategy, these extensions may be available for both `ResiliencePipelineBuilder` and `ResiliencePipelineBuilder<T>` or just one of them. Proactive strategies like timeout or rate limiter are available for both types of builders, while specialized reactive strategies are only available for `ResiliencePipelineBuilder<T>`. Adding multiple resilience strategies is supported. | ||
|
||
Each resilience strategy provides: | ||
|
||
- Extensions for the resilience strategy builders. | ||
- Configuration options (e.g., `RetryStrategyOptions`) to specify the strategy's behavior. | ||
|
||
Here's an simple example: | ||
|
||
<!-- snippet: resilience-strategy-sample --> | ||
```cs | ||
ResiliencePipeline pipeline = new ResiliencePipelineBuilder() | ||
.AddTimeout(new TimeoutStrategyOptions | ||
{ | ||
Timeout = TimeSpan.FromSeconds(5) | ||
}) | ||
.Build(); | ||
``` | ||
<!-- endSnippet --> | ||
|
||
> [!NOTE] | ||
> The configuration options are automatically validated by Polly and come with sensible defaults. Therefore, you don't have to specify all the properties unless needed. | ||
## Fault-handling in reactive strategies | ||
|
||
Each reactive strategy exposes the `ShouldHandle` predicate property. This property represents a predicate to determine whether the fault or the result returned after executing the resilience strategy should be managed or not. | ||
|
||
This is demonstrated bellow: | ||
|
||
<!-- snippet: should-handle --> | ||
```cs | ||
// Create an instance of options for a retry strategy. In this example, | ||
// we use RetryStrategyOptions. You could also use other options like | ||
// CircuitBreakerStrategyOptions or FallbackStrategyOptions. | ||
var options = new RetryStrategyOptions<HttpResponseMessage>(); | ||
|
||
// PredicateBuilder can simplify the setup of the ShouldHandle predicate. | ||
options.ShouldHandle = new PredicateBuilder<HttpResponseMessage>() | ||
.HandleResult(response => !response.IsSuccessStatusCode) | ||
.Handle<HttpRequestException>(); | ||
|
||
// For greater flexibility, you can directly use the ShouldHandle delegate with switch expressions. | ||
options.ShouldHandle = args => args.Outcome switch | ||
{ | ||
// Strategies may offer additional context for result handling. | ||
// For instance, the retry strategy exposes the number of attempts made. | ||
_ when args.AttemptNumber > 3 => PredicateResult.False(), | ||
{ Exception: HttpRequestException } => PredicateResult.True(), | ||
{ Result: HttpResponseMessage response } when !response.IsSuccessStatusCode => PredicateResult.True(), | ||
_ => PredicateResult.False() | ||
}; | ||
``` | ||
<!-- endSnippet --> | ||
|
||
Some additional notes from the preceding example: | ||
|
||
- `PredicateBuilder` is a utility API designed to make configuring predicates easier. | ||
- `PredicateResult.True()` is a shorthand for `new ValueTask<bool>(true)`. | ||
- All `ShouldHandle` predicates are asynchronous and have the type `Func<Args<TResult>, ValueTask<bool>>`. The `Args<TResult>` serves as a placeholder, and each strategy defines its own arguments. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.