docs/strategies/index.md
Resilience strategies are essential components of Polly, designed to execute user-defined callbacks while adding an extra layer of resilience. These strategies can't be executed directly; they must be run through a resilience pipeline. Polly provides an API to construct resilience pipelines by incorporating one or more resilience strategies through the pipeline builders.
Polly categorizes resilience strategies into two main groups:
| Strategy | Premise | AKA | How does the strategy mitigate? |
|---|---|---|---|
| Retry | Many faults are transient and may self-correct after a short delay. | Maybe it's just a blip | Allows configuring automatic retries. |
| Circuit-breaker | When a system is seriously struggling, failing fast is better than making users/callers wait. |
Protecting a faulting system from overload can help it recover. | Stop doing it if it hurts
Give that system a break | Breaks the circuit (blocks executions) for a period, when faults exceed some pre-configured threshold. | | Fallback | Things will still fail - plan what you will do when that happens. | Degrade gracefully | Defines an alternative value to be returned (or action to be executed) on failure. | | Hedging | Things can be slow sometimes, plan what you will do when that happens. | Hedge your bets | Executes parallel actions when things are slow and waits for the fastest one. |
| Strategy | Premise | AKA | How does the strategy prevent? |
|---|---|---|---|
| Timeout | Beyond a certain wait, a success result is unlikely. | Don't wait forever | Guarantees the caller won't have to wait beyond the timeout. |
| Rate Limiter | Limiting the rate a system handles requests is another way to control load. |
This can apply to the way your system accepts incoming calls, and/or to the way you call downstream services. | Slow down a bit, will you? | Constrains executions to not exceed a certain rate. |
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 for the latter one. Adding multiple resilience strategies is supported.
| Strategy | ResiliencePipelineBuilder | ResiliencePipelineBuilder<T> |
|---|---|---|
| Circuit Breaker | ✅ | ✅ |
| Fallback | ❌ | ✅ |
| Hedging | ❌ | ✅ |
| Rate Limiter | ✅ | ✅ |
| Retry | ✅ | ✅ |
| Timeout | ✅ | ✅ |
Each resilience strategy provides:
RetryStrategyOptions) to specify the strategy's behavior.Here's an simple example:
<!-- snippet: resilience-strategy-sample -->ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
.AddTimeout(new TimeoutStrategyOptions
{
Timeout = TimeSpan.FromSeconds(5)
})
.Build();
[!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.
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.
Setting up the predicate can be accomplished in the following ways:
PredicateBuilder: The PredicateBuilder{<TResult>} classes provide a more straight-forward method to configure the predicates, akin to predicate setups in earlier Polly versions.The examples below illustrate these:
var options = new RetryStrategyOptions<HttpResponseMessage>
{
// For greater flexibility, you can directly use the ShouldHandle delegate with switch expressions.
ShouldHandle = args => args.Outcome switch
{
{ Exception: HttpRequestException } => PredicateResult.True(),
{ Exception: TimeoutRejectedException } => PredicateResult.True(), // You can handle multiple exceptions
{ Result: HttpResponseMessage response } when !response.IsSuccessStatusCode => PredicateResult.True(),
_ => PredicateResult.False()
}
};
Notes from the preceding example:
PredicateResult.True() is a shorthand for new ValueTask<bool>(true).ShouldHandle predicates are asynchronous and use the type Func<Args<TResult>, ValueTask<bool>>.
Args<TResult> acts as a placeholder, and each strategy defines its own arguments.[!NOTE] The
argsparameter of theShouldHandleallows read-only access to strategy specific information. For example in case of retry you can access theAttemptNumber, as well as theOutcomeandContext.
You can also use asynchronous delegates for more advanced scenarios, such as retrying based on the response body:
<!-- snippet: 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
};
}
// Determine whether to retry asynchronously or not based on the result.
return await ShouldRetryAsync(args.Outcome.Result!, args.Context.CancellationToken);
}
};
xref:Polly.PredicateBuilder, or xref:Polly.PredicateBuilder`1, is a utility class aimed at simplifying the configuration of predicates:
<!-- snippet: should-handle-predicate-builder -->// 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
};
The preceding sample:
HandleResult to register a predicate that determines whether the result should be handled or not.Handle to handle multiple exceptions types.[!NOTE] When using
PredicateBuilderinstead of manually configuring the predicate, there is a minor performance impact. Each method call onPredicateBuilderregisters a new predicate, which must be invoked when evaluating the outcome.