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

[Docs] Expand fault handling docs #1675

Merged
merged 8 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ We are a member of the [.NET Foundation](https://www.dotnetfoundation.org/about)

## Documentation

This README aims to give a quick overview of some Polly features - including enough to get you started with any resilience strategy. For deeper detail on any resilience strategy, and many other aspects of Polly, be sure also to check out the [documentation][polly-docs].
This README aims to give a quick overview of some Polly features - including enough to get you started with any resilience strategy. For deeper detail on any resilience strategy, and many other aspects of Polly, be sure also to check out the [pollydocs.org][polly-docs].
martincostello marked this conversation as resolved.
Show resolved Hide resolved

## Quick start

Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ You can learn more about each strategy and how to use them [resilience strategie

## How to get started with Polly?

Polly is easy to install and use. You can follow the [getting started](getting-started.md) guide to add Polly to your project and use it for your requests.
Polly is easy to install and use. You can follow the [getting started](getting-started.md) guide to add and start using Polly in your projects.

## Where to find more information?

Expand Down
106 changes: 80 additions & 26 deletions docs/strategies/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,39 +43,93 @@ ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
> [!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
## Fault handling

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.
Each reactive strategy provides access to the `ShouldHandle` predicate property. This property offers a mechanism to decide whether the resilience strategy should manage the fault or result returned after execution.

This is demonstrated below:
Setting up the predicate can be accomplished in the following ways:

<!-- snippet: should-handle -->
- **Manually setting the predicate**: Directly configure the predicate. The advised approach involves using [switch expressions](https://learn.microsoft.com/dotnet/csharp/language-reference/operators/switch-expression) for maximum flexibility, and also allows the incorporation of asynchronous predicates.
- **Employing `PredicateBuilder`**: The `PredicateBuilder` provides a more straightforward method to configure the predicates, akin to predicate setups in earlier Polly versions.
martincostello marked this conversation as resolved.
Show resolved Hide resolved

The examples below illustrate these:

### Predicates

<!-- snippet: should-handle-manual -->
```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
var options = new RetryStrategyOptions<HttpResponseMessage>
{
// 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()
// For greater flexibility, you can directly use the ShouldHandle delegate with switch expressions.
ShouldHandle = args => args.Outcome switch
{
// Strategies may offer rich arguments for result handling.
// For instance, the retry strategy exposes the number of attempts made.
_ when args.AttemptNumber > 3 => PredicateResult.False(),
{ Exception: HttpRequestException } => PredicateResult.True(),
{ Exception: TimeoutRejectedException } => PredicateResult.True(), // You can handle multiple exceptions
{ Result: HttpResponseMessage response } when !response.IsSuccessStatusCode => PredicateResult.True(),
_ => PredicateResult.False()
}
};
```
<!-- endSnippet -->

Some additional notes from the preceding example:
Notes from the preceding example:

- Switch expressions are used to determine whether to retry on not.
- `PredicateResult.True()` is a shorthand for `new ValueTask<bool>(true)`.
- `ShouldHandle` predicates are asynchronous and use the type `Func<Args<TResult>, ValueTask<bool>>`. The `Args<TResult>` acts as a placeholder, and each strategy defines its own arguments.
- Multiple exceptions can be handled using switch expressions.

### Asynchronous predicates

You can also use asynchronous delegates for more advanced scenarios, such as retrying based on the response body:

- `PredicateBuilder` is a utility API designed to make configuring predicates easier.
- `PredicateResult.True()` is 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.
<!-- snippet: should-handle-manual-async -->
```cs
var options = new RetryStrategyOptions<HttpResponseMessage>
{
ShouldHandle = async args =>
{
if (args.Outcome.Exception is not null)
{
return args.Outcome.Exception switch
{
HttpRequestException => true,
TimeoutRejectedException => true,
_ => false
};
}

// Determine whether to retry asynchronously or not based on the result.
return await ShouldRetryAsync(args.Outcome.Result!, args.Context.CancellationToken);
}
};
```
<!-- endSnippet -->

### Predicate builder

<xref:Polly.PredicateBuilder>, or <xref:Polly.PredicateBuilder`1>, is a utility class aimed at simplifying the configuration of predicates:

<!-- snippet: should-handle-predicate-builder -->
```cs
// Use PredicateBuilder<HttpResponseMessage> to simplify the setup of the ShouldHandle predicate.
var options = new RetryStrategyOptions<HttpResponseMessage>
{
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.HandleResult(response => !response.IsSuccessStatusCode) // Handle results
.Handle<HttpRequestException>() // Or handle exception
.Handle<TimeoutRejectedException>() // Chaining is supported
};
```
<!-- endSnippet -->

The preceding sample:

- Uses `HandleResult` to register predicate that determines whether the result should be handled or not.
martincostello marked this conversation as resolved.
Show resolved Hide resolved
- Uses `Handle` to handle multiple exceptions types.

> [!NOTE]
> When using `PredicateBuilder` instead of manually configuring the predicate, there is a minor performance impact. Each method call on `PredicateBuilder` registers a new predicate, which must be invoked when evaluating the outcome.
74 changes: 56 additions & 18 deletions src/Snippets/Docs/ResilienceStrategies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,67 @@ public static async Task Usage()
#endregion
}

public static void ShouldHandle()
public static void ShouldHandleManual()
{
#region should-handle
#region should-handle-manual

// 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>();
var options = new RetryStrategyOptions<HttpResponseMessage>
{
// For greater flexibility, you can directly use the ShouldHandle delegate with switch expressions.
ShouldHandle = args => args.Outcome switch
{
// Strategies may offer rich arguments for result handling.
// For instance, the retry strategy exposes the number of attempts made.
_ when args.AttemptNumber > 3 => PredicateResult.False(),
{ Exception: HttpRequestException } => PredicateResult.True(),
{ Exception: TimeoutRejectedException } => PredicateResult.True(), // You can handle multiple exceptions
{ Result: HttpResponseMessage response } when !response.IsSuccessStatusCode => PredicateResult.True(),
_ => PredicateResult.False()
}
};

#endregion
}

public static void ShouldHandleManualAsync()
{
#region should-handle-manual-async

var options = new RetryStrategyOptions<HttpResponseMessage>
{
ShouldHandle = async args =>
{
if (args.Outcome.Exception is not null)
{
return args.Outcome.Exception switch
{
HttpRequestException => true,
TimeoutRejectedException => true,
_ => false
};
}

// PredicateBuilder can simplify the setup of the ShouldHandle predicate.
options.ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.HandleResult(response => !response.IsSuccessStatusCode)
.Handle<HttpRequestException>();
// Determine whether to retry asynchronously or not based on the result.
return await ShouldRetryAsync(args.Outcome.Result!, args.Context.CancellationToken);
}
};

#endregion
}

private static Task<bool> ShouldRetryAsync(HttpResponseMessage response, CancellationToken cancellationToken) => Task.FromResult(true);

public static void ShouldHandlePredicateBuilder()
{
#region should-handle-predicate-builder

// For greater flexibility, you can directly use the ShouldHandle delegate with switch expressions.
options.ShouldHandle = args => args.Outcome switch
// Use PredicateBuilder<HttpResponseMessage> to simplify the setup of the ShouldHandle predicate.
var options = new RetryStrategyOptions<HttpResponseMessage>
{
// 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()
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.HandleResult(response => !response.IsSuccessStatusCode) // Handle results
.Handle<HttpRequestException>() // Or handle exception
.Handle<TimeoutRejectedException>() // Chaining is supported
};

#endregion
Expand Down