Skip to content

Commit

Permalink
Prepare v8 README and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
martintmk committed Sep 5, 2023
1 parent 549000b commit 729e40a
Show file tree
Hide file tree
Showing 18 changed files with 1,700 additions and 14 deletions.
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@
> Major performance improvements are on the way! Please see our [blog post](https://www.thepollyproject.org/2023/03/03/we-want-your-feedback-introducing-polly-v8/) to learn more and provide feedback in the [related GitHub issue](https://github.com/App-vNext/Polly/issues/1048).
>
> :rotating_light::rotating_light: **Polly v8 feature-complete!** :rotating_light::rotating_light:
> - Polly v8 Beta 1 is now available on [NuGet.org](https://www.nuget.org/packages/Polly/8.0.0-beta.1)
> - Polly v8 Beta 1 is now available on [NuGet.org](https://www.nuget.org/packages/Polly/8.0.0-beta.1).
> - The Beta 1 version is considered feature-complete and the public API surface is stable.
> - The v8 docs are not yet finished, but you can take a look at sample code in these locations:
> - Within the repo's new [Samples folder](https://github.com/App-vNext/Polly/tree/main/samples)
> - By reading `Polly.Core`'s [README](https://github.com/App-vNext/Polly/blob/main/src/Polly.Core/README.md)
> - Explore the [v8 documentation](README_V8.md).
# Polly

Expand Down
557 changes: 557 additions & 0 deletions README_V8.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ If you're already familiar with the [basic features](../README.md) of Polly, del
- [Simmy](simmy.md): Get to know chaos engineering via Polly's capabilities.
- [Third-Party Libraries and Contributions](libraries-and-contributions.md): Find out which libraries Polly depends on and who contributes to its development.
- [Additional Resources](resources.md): Browse through blogs, podcasts, courses, e-books, and other community resources.
- [Using Polly with HttpClientFactory](https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory): For using Polly with HttpClientFactory in ASP.NET Core 2.1 and later versions.
136 changes: 136 additions & 0 deletions docs/general.md
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.
128 changes: 128 additions & 0 deletions docs/resilience-pipelines.md
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.
63 changes: 63 additions & 0 deletions docs/resilience-strategies.md
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.
2 changes: 1 addition & 1 deletion docs/simmy.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
[Simmy][simmy] is a major new companion project adding a chaos-engineering and fault-injection dimension to Polly, through the provision of policies to selectively inject faults or latency.
Head over to the [Simmy][simmy] repo to find out more.

[simmy]: https://github.com/Polly-Contrib/Simmy
[simmy]: https://github.com/Polly-Contrib/Simmy
Loading

0 comments on commit 729e40a

Please sign in to comment.