Skip to main content

Introduction

Polly can be extended with custom resilience strategies to meet your specific needs. This guide explains how to create both reactive and proactive resilience strategies.

Strategy Types

Polly identifies two types of resilience strategies:

Reactive Strategies

Handle specific exceptions or results returned by callbacks

Proactive Strategies

Make proactive decisions to cancel or reject callback execution

Core Components

Every resilience strategy requires these components:
1

Strategy Implementation

The strategy class that inherits from ResilienceStrategy or ResilienceStrategy<T>
2

Options Class

Configuration options that inherit from ResilienceStrategyOptions
3

Builder Extensions

Extension methods for ResiliencePipelineBuilder or ResiliencePipelineBuilder<T>
4

Arguments Types

Custom structs that encapsulate event information for delegates

Component Architecture

The diagram below shows how custom components interact with Polly’s built-in types:

Delegates

Resilience strategies use three types of delegates:

Predicates

Determine whether a strategy should handle a given execution result.
Func<Args<TResult>, ValueTask<bool>>

Events

Triggered when significant actions or states occur.
  • Reactive: Func<Args<TResult>, ValueTask>
  • Proactive: Func<Args, ValueTask>

Generators

Invoked when the strategy needs specific values from the caller.
  • Reactive: Func<Args<TResult>, ValueTask<TValue>>
  • Proactive: Func<Args, ValueTask<TValue>>
All delegates are asynchronous and return a ValueTask. When setting up delegates, consider using ResilienceContext.ContinueOnCapturedContext if your code interacts with a synchronization context.

Delegate Usage Examples

new ResiliencePipelineBuilder()
    .AddRetry(new RetryStrategyOptions
    {
        // Non-Generic predicate for multiple result types
        ShouldHandle = args => args.Outcome switch
        {
            { Exception: InvalidOperationException } => PredicateResult.True(),
            { Result: string result } when result == "Failure" => PredicateResult.True(),
            { Result: int result } when result == -1 => PredicateResult.True(),
            _ => PredicateResult.False()
        },
    })
    .Build();

Arguments

Arguments flow information from the strategy to delegate consumers. They should:
  • Always have an Arguments suffix
  • Include a Context property
  • Be implemented as readonly structs

Example: Proactive Arguments

public readonly struct OnThresholdExceededArguments
{
    public OnThresholdExceededArguments(ResilienceContext context, TimeSpan threshold, TimeSpan duration)
    {
        Context = context;
        Threshold = threshold;
        Duration = duration;
    }

    public TimeSpan Threshold { get; }

    public TimeSpan Duration { get; }

    // All arguments must provide a Context property
    public ResilienceContext Context { get; }
}
Using arguments boosts extensibility and maintainability, as adding new members becomes a non-breaking change.

Next Steps

Custom Reactive Strategy

Create a strategy that handles specific results or exceptions

Custom Proactive Strategy

Build a strategy that makes proactive execution decisions

Build docs developers (and LLMs) love