Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the v8 README.md #1548

Merged
merged 9 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 3 additions & 13 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 Expand Up @@ -56,10 +54,6 @@ dotnet add package Polly

For details of supported compilation targets by version, see the [supported targets](https://github.com/App-vNext/Polly/wiki/Supported-targets) grid.

### Using Polly with HttpClient factory from ASP.NET Core 2.1

For using Polly with HttpClient factory from ASP.NET Core 2.1, see our [detailed wiki page](https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory), then come back here or [explore the wiki](https://github.com/App-vNext/Polly/wiki) to learn more about the operation of each policy.

### Role of the readme and the wiki

This ReadMe aims to give a quick overview of all Polly features - including enough to get you started with any policy. For deeper detail on any policy, and many other aspects of Polly, be sure also to check out the [wiki documentation](https://github.com/App-vNext/Polly/wiki).
Expand Down Expand Up @@ -1065,8 +1059,4 @@ Licensed under the terms of the [New BSD License](http://opensource.org/licenses

## Resources

- [Extensibility](docs/v7/extensibility.md): Learn how you can extend Polly with new policies.
- [Polly-Contrib](docs/polly-contrib.md): Learn how you can contribute to and enhance the Polly ecosystem.
- [Simmy](docs/simmy.md): Understand chaos engineering through the use of Polly.
- [3rd Party Libraries and Contributions](docs/libraries-and-contributions.md): Discover the libraries that Polly relies on and the contributors who help improve it.
- [Blogs, Podcasts, Courses, E-books, etc.](docs/resources.md): Explore additional community resources.
Visit the [documentation](docs/README.md) to explore more Polly-related resources.
549 changes: 549 additions & 0 deletions README_V8.md

Large diffs are not rendered by default.

16 changes: 14 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@

If you're already familiar with the [basic features](../README.md) of Polly, delve deeper into its advanced functionalities here.

## Table of Contents
## Topics

- [Extensibility](v7/extensibility.md): Learn how you can extend Polly with new policies.
- [General](general.md): General information about Polly.
- [Resilience Pipelines](resilience-pipelines.md): Understanding the use of resilience pipelines.
- [Resilience Strategies](resilience-strategies.md): General information about resilience strategies and how to configure them.
- [Resilience Context](resilience-context.md): Describes the resilience context used by resilience pipelines and strategies.
- [Resilience Pipeline Registry](resilience-pipeline-registry.md): Exploring the registry that stores resilience pipelines.
- [Dependency Injection](dependency-injection.md): How Polly integrates with Dependency Injection.
- [Telemetry](telemetry.md): Insights into telemetry generated by resilience pipelines and strategies.
- [Extensibility](v7/extensibility.md): Learn how you can extend Polly with new resilience strategies.
- [Polly-Contrib](polly-contrib.md): Learn how to contribute to and extend the Polly ecosystem.
- [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.

## Topics (older Polly versions)
martincostello marked this conversation as resolved.
Show resolved Hide resolved

- [Extensibility (v7)](v7/extensibility.md): Learn how you can extend Polly with new policies.
3 changes: 3 additions & 0 deletions docs/dependency-injection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Dependency Injection

TODO
martincostello marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 3 additions & 0 deletions docs/extensiblity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Extensibility

TODO
martincostello marked this conversation as resolved.
Show resolved Hide resolved
68 changes: 68 additions & 0 deletions docs/general.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# General
martincostello marked this conversation as resolved.
Show resolved Hide resolved

## 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
Polly targets .NET Standard 2.0+ ([coverage](https://docs.microsoft.com/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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update the TFMs for v8?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


For details of supported compilation targets by version, see the [supported targets](https://github.com/App-vNext/Polly/wiki/Supported-targets) grid.

## 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.
martincostello marked this conversation as resolved.
Show resolved Hide resolved

<!-- 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.
martincostello marked this conversation as resolved.
Show resolved Hide resolved
85 changes: 85 additions & 0 deletions docs/resilience-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Resilience Context

The `ResilienceContext` class in Polly provides an execution-scoped instance that accompanies each execution through a Polly resilience strategy. This class serves to share context and facilitate information exchange between the pre-execution, mid-execution, and post-execution phases.

The resilience context exposes several properties:

- `OperationKey`: A user-defined identifier for the operation.
- `CancellationToken`: The cancellation token linked to the operation.
- `Properties`: An instance of `ResilienceProperties` for attaching custom data to the context.
- `ContinueOnCapturedContext`: Specifies whether the asynchronous execution should continue on the captured context.

## Usage

Below is an example demonstrating how to work with `ResilienceContext`:

<!-- snippet: resilience-context -->
```cs
// Retrieve a context with a cancellation token
ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken);

// Attach custom data to the context
var key1 = new ResiliencePropertyKey<string>("my-key-1");
var key2 = new ResiliencePropertyKey<int>("my-key-2");

context.Properties.Set(key1, "my-data");
context.Properties.Set(key2, 123);

// Utilize the context in a resilience pipeline
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
.AddRetry(new()
{
OnRetry = args =>
{
// Retrieve custom data from the context, if available
if (args.Context.Properties.TryGetValue(key1, out var data))
{
Console.WriteLine("OnRetry, Custom Data: {0}", data);
}

return default;
}
})
.Build();

// Execute the resilience pipeline asynchronously
await pipeline.ExecuteAsync(
async context =>
{
// Insert your execution logic here
},
context);

// Return the context to the pool
ResilienceContextPool.Shared.Return(context);
```
<!-- endSnippet -->

## Resilient context pooling
martincostello marked this conversation as resolved.
Show resolved Hide resolved

<!-- Overview -->
The `ResilienceContext` object is resource-intensive to create, and recreating it for each execution would negatively impact performance. To address this issue, Polly provides a `ResilienceContextPool`. This pool allows you to obtain and reuse `ResilienceContext` instances. Once you've finished using a context instance, you can return it to the pool. This action will reset the context to its initial state, making it available for reuse.

<!-- Methods -->
The `ResilienceContextPool` offers several `Get` methods. These methods not only allow you to retrieve a `ResilienceContext` instance, but also enable you to initialize some of its properties at the time of retrieval.

<!-- snippet: resilience-context-pool -->
```cs
// Retrieve a context with a cancellation token
ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken);

// Retrieve a context with a specific operation key
context = ResilienceContextPool.Shared.Get("my-operation-key", cancellationToken);

// Retrieve a context with multiple properties
context = ResilienceContextPool.Shared.Get(
operationKey: "my-operation-key",
continueOnCapturedContext: true,
cancellationToken: cancellationToken);

// Use the pool here

// Return the context to the pool
ResilienceContextPool.Shared.Return(context);
```
<!-- endSnippet -->
15 changes: 15 additions & 0 deletions docs/resilience-pipeline-registry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Resilience Pipeline Registry

The `ResiliencePipelineRegistry<TKey>` is a generic class that provides the following functionalities:

- Thread-safe retrieval and dynamic creation of both generic and non-generic resilience pipelines.
- Dynamic reloading of resilience pipelines when configurations change.
- Support for registering both generic and non-generic resilience pipeline builders, enabling dynamic pipeline instance creation.
- Automatic resource management, including disposal of resources tied to resilience pipelines.

> [!NOTE]
> The generic `TKey` parameter specifies the key type used for caching individual resilience pipelines within the registry. In most use-cases, you will be working with `ResiliencePipelineRegistry<string>`.

## Usage

TODO
martincostello marked this conversation as resolved.
Show resolved Hide resolved
140 changes: 140 additions & 0 deletions docs/resilience-pipelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Resilience pipelines

The `ResiliencePipeline` allows executing arbitrary user-provided callbacks. It is a combination of one or more resilience strategies.

## Usage

The `ResiliencePipeline` allow executing various synchronous and asynchronous user-provided callbacks as seen in the examples bellow:
martincostello marked this conversation as resolved.
Show resolved Hide resolved

<!-- 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> 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.
> 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.


## Empty resilience pipeline

<!-- Explanation of the Empty Resilience Pipeline -->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<!-- Explanation of the Empty Resilience Pipeline -->

The empty resilience pipeline is a special construct that lacks any resilience strategies. You can access it through the following ways:

- `ResiliencePipeline.Empty`
- `ResiliencePipeline<T>.Empty`

This is particularly useful in test scenarios where implementing resilience strategies could slow down the test execution.
martincostello marked this conversation as resolved.
Show resolved Hide resolved

## 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.
martincostello marked this conversation as resolved.
Show resolved Hide resolved

<!-- 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.
martincostello marked this conversation as resolved.
Show resolved Hide resolved
Loading