Migration guide from v7 to v8

Welcome to the migration guide for Polly's v8 release. Version 8 of Polly brings major new enhancements and supports all of the same scenarios as previous versions. In the following sections, we'll detail the differences between the v7 and v8 APIs, and provide steps on how to transition smoothly.

Note

The v7 API is still available and fully supported even when using the v8 version by referencing the Polly package.

Major differences

  • The term Policy is now replaced with Strategy: In previous versions, Polly used the term policy for retries, timeouts, etc. In v8, these are referred to as resilience strategies.
  • Introduction of Resilience Pipelines: A resilience pipeline combines one or more resilience strategies. This is the foundational API for Polly v8, similar to the Policy Wrap in previous versions but integrated into the core API.
  • Unified sync and async flows: Interfaces such as IAsyncPolicy, IAsyncPolicy<T>, ISyncPolicy, ISyncPolicy<T>, and IPolicy are now unified under ResiliencePipeline and ResiliencePipeline<T>. The resilience pipeline supports both synchronous and asynchronous execution flows.
  • Native async support: Polly v8 was designed with asynchronous support from the start.
  • No static APIs: Unlike previous versions, v8 doesn't use static APIs. This improves testability and extensibility while maintaining ease of use.
  • Options-based configuration: Configuring individual resilience strategies is now options-based, offering more flexibility and improving maintainability and extensibility.
  • Built-in telemetry: Polly v8 now has built-in telemetry support.
  • Improved performance and low-allocation APIs: Polly v8 brings significant performance enhancements and provides zero-allocation APIs for advanced use cases.
Note

Please read the comments in the code carefully for additional context and explanations.

Polly or Polly.Core package

When you do your migration process it is recommended to follow these steps:

  • Upgrade the Polly package version from 7.x to 8.x
    • Your previous policies should run smoothly without any change
  • Migrate your V7 policies to V8 strategies gradually, such as one at a time
    • Test your migrated code thoroughly
  • After you have successfully migrated all your legacy Polly code then change your package reference from Polly to Polly.Core

Migrating execution policies

This section describes how to migrate from execution policies (i.e. IAsyncPolicy, ISyncPolicy) to resilience pipelines (i.e. ResiliencePipeline, ResiliencePipeline<T>).

Configuring policies in v7

In earlier versions, Polly exposed various interfaces to execute user code:

  • IAsyncPolicy
  • IAsyncPolicy<T>
  • ISyncPolicy
  • ISyncPolicy<T>

These interfaces were created and used as shown below:

// Create and use the ISyncPolicy.
ISyncPolicy syncPolicy = Policy
    .Handle<Exception>()
    .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1));

syncPolicy.Execute(() =>
{
    // Your code goes here
});

// Create and use the IAsyncPolicy
IAsyncPolicy asyncPolicy = Policy
    .Handle<Exception>()
    .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));
await asyncPolicy.ExecuteAsync(async token =>
{
    // Your code goes here
}, cancellationToken);

// Create and use the ISyncPolicy<T>
ISyncPolicy<HttpResponseMessage> syncPolicyT = Policy<HttpResponseMessage>
    .HandleResult(result => !result.IsSuccessStatusCode)
    .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1));

syncPolicyT.Execute(() =>
{
    // Your code goes here
    return GetResponse();
});

// Create and use the IAsyncPolicy<T>
IAsyncPolicy<HttpResponseMessage> asyncPolicyT = Policy<HttpResponseMessage>
    .HandleResult(result => !result.IsSuccessStatusCode)
    .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));

await asyncPolicyT.ExecuteAsync(async token =>
{
    // Your code goes here
    return await GetResponseAsync(token);
}, cancellationToken);

Configuring strategies in v8

In Polly v8, there are no such interfaces. The previous samples become:

// Create and use the ResiliencePipeline.
//
// Use the ResiliencePipelineBuilder to start building the resilience pipeline
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
    .AddRetry(new RetryStrategyOptions
    {
        ShouldHandle = new PredicateBuilder().Handle<Exception>(),
        Delay = TimeSpan.FromSeconds(1),
        MaxRetryAttempts = 3,
        BackoffType = DelayBackoffType.Constant
    })
    .Build(); // After all necessary strategies are added, call Build() to create the pipeline.

// Synchronous execution
pipeline.Execute(static () =>
{
    // Your code goes here
});

// Asynchronous execution is also supported with the same pipeline instance
await pipeline.ExecuteAsync(static async token =>
{
    // Your code goes here
}, cancellationToken);

// Create and use the ResiliencePipeline<T>.
//
// Building of generic resilience pipeline is very similar to non-generic one.
// Notice the use of generic RetryStrategyOptions<HttpResponseMessage> to configure the strategy.
ResiliencePipeline<HttpResponseMessage> pipelineT = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddRetry(new RetryStrategyOptions<HttpResponseMessage>
    {
        ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
            .Handle<Exception>()
            .HandleResult(static result => !result.IsSuccessStatusCode),
        Delay = TimeSpan.FromSeconds(1),
        MaxRetryAttempts = 3,
        BackoffType = DelayBackoffType.Constant
    })
    .Build();

// Synchronous execution
pipelineT.Execute(static () =>
{
    // Your code goes here
    return GetResponse();
});

// Asynchronous execution
await pipelineT.ExecuteAsync(static async token =>
{
    // Your code goes here
    return await GetResponseAsync(token);
}, cancellationToken);
Tip

Things to remember:

  • Use ResiliencePipelineBuilder{<TResult>} to build a resiliency pipeline
  • Use one of the Add* builder methods to add a new strategy to the pipeline
  • Use either Execute or ExecuteAsync depending on the execution context

For further information please check out the Resilience pipelines documentation.

Migrating policy wrap

Policy wrap in v7

Policy wrap is used to combine multiple policies into one:

IAsyncPolicy retryPolicy = Policy.Handle<Exception>().WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));

IAsyncPolicy timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(3));

// Wrap the policies. The policies are executed in the following order:
// 1. Retry <== outer
// 2. Timeout <== inner
IAsyncPolicy wrappedPolicy = Policy.WrapAsync(retryPolicy, timeoutPolicy);

Policy wrap in v8

In v8, there's no need to use policy wrap explicitly. Instead, policy wrapping is integrated into ResiliencePipelineBuilder:

// The "PolicyWrap" is integrated directly. The strategies are executed in the following order:
// 1. Retry <== outer
// 2. Timeout <== inner
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
    .AddRetry(new()
    {
        MaxRetryAttempts = 3,
        Delay = TimeSpan.FromSeconds(1),
        BackoffType = DelayBackoffType.Constant,
        ShouldHandle = new PredicateBuilder().Handle<Exception>()
    })
    .AddTimeout(TimeSpan.FromSeconds(3))
    .Build();

See fallback after retries for an example on how the strategies are executed.

Tip

Things to remember:

  • Use ResiliencePipelineBuilder{<TResult>} to build a resiliency pipeline
  • Use multiple Add* builder methods to add new strategies to your pipeline

For further information please check out the Resilience pipelines documentation.

Migrating retry policies

This section describes how to migrate v7 retry policies to V8 retry strategies.

Retry in v7

In v7 the retry policy is configured as:

// Retry once
Policy
    .Handle<SomeExceptionType>()
    .Retry();

// Retry multiple times
Policy
    .Handle<SomeExceptionType>()
    .Retry(3);

// Retry multiple times with callback
Policy
    .Handle<SomeExceptionType>()
    .Retry(3, onRetry: (exception, retryCount) =>
    {
        // Add logic to be executed before each retry, such as logging
    });

// Retry forever
Policy
    .Handle<SomeExceptionType>()
    .RetryForever();

Retry in v8

In v8 the retry strategy is configured as:

// Retry once
//
// Because we are adding retries to a non-generic pipeline,
// we use the non-generic RetryStrategyOptions.
new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions
{
    // PredicateBuilder is used to simplify the initialization of predicates.
    // Its API should be familiar to the v7 way of configuring what exceptions to handle.
    ShouldHandle = new PredicateBuilder().Handle<SomeExceptionType>(),
    MaxRetryAttempts = 1,
    // To disable waiting between retries, set the Delay property to TimeSpan.Zero.
    Delay = TimeSpan.Zero,
})
.Build();

// Retry multiple times
new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions
{
    ShouldHandle = new PredicateBuilder().Handle<SomeExceptionType>(),
    MaxRetryAttempts = 3,
    Delay = TimeSpan.Zero,
})
.Build();

// Retry multiple times with callback
new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions
{
    ShouldHandle = new PredicateBuilder().Handle<SomeExceptionType>(),
    MaxRetryAttempts = 3,
    Delay = TimeSpan.Zero,
    OnRetry = static args =>
    {
        // Add logic to be executed before each retry, such as logging
        return default;
    }
})
.Build();

// Retry forever
new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions
{
    ShouldHandle = new PredicateBuilder().Handle<SomeExceptionType>(),
    // To retry forever, set the MaxRetryAttempts property to int.MaxValue.
    MaxRetryAttempts = int.MaxValue,
    Delay = TimeSpan.Zero,
})
.Build();

Retry and wait in v7

// Wait and retry multiple times
Policy
    .Handle<SomeExceptionType>()
    .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1));

// Wait and retry multiple times with callback
Policy
    .Handle<SomeExceptionType>()
    .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1), onRetry: (exception, retryCount) =>
    {
        // Add logic to be executed before each retry, such as logging
    });

// Wait and retry forever
Policy
    .Handle<SomeExceptionType>()
    .WaitAndRetryForever(_ => TimeSpan.FromSeconds(1));

Retry and wait in v8

// Wait and retry multiple times
new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions
{
    ShouldHandle = new PredicateBuilder().Handle<SomeExceptionType>(),
    MaxRetryAttempts = 3,
    Delay = TimeSpan.FromSeconds(1),
    BackoffType = DelayBackoffType.Constant
})
.Build();

// Wait and retry multiple times with callback
new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions
{
    ShouldHandle = new PredicateBuilder().Handle<SomeExceptionType>(),
    MaxRetryAttempts = 3,
    Delay = TimeSpan.FromSeconds(1),
    BackoffType = DelayBackoffType.Constant,
    OnRetry = static args =>
    {
        // Add logic to be executed before each retry, such as logging
        return default;
    }
})
.Build();

// Wait and retry forever
new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions
{
    ShouldHandle = new PredicateBuilder().Handle<SomeExceptionType>(),
    MaxRetryAttempts = int.MaxValue,
    Delay = TimeSpan.FromSeconds(1),
    BackoffType = DelayBackoffType.Constant
})
.Build();

Retry results in v7

// Wait and retry with result handling
Policy
    .Handle<SomeExceptionType>()
    .OrResult<HttpResponseMessage>(result => result.StatusCode == HttpStatusCode.InternalServerError)
    .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1));

Retry results in v8

// Shows how to add a retry strategy that also retries particular results.
new ResiliencePipelineBuilder<HttpResponseMessage>().AddRetry(new RetryStrategyOptions<HttpResponseMessage>
{
    // PredicateBuilder is a convenience API that can used to configure the ShouldHandle predicate.
    ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
        .Handle<SomeExceptionType>()
        .HandleResult(static result => result.StatusCode == HttpStatusCode.InternalServerError),
    MaxRetryAttempts = 3,
})
.Build();

// The same as above, but using the switch expressions for best performance.
new ResiliencePipelineBuilder<HttpResponseMessage>().AddRetry(new RetryStrategyOptions<HttpResponseMessage>
{
    // Determine what results to retry using switch expressions.
    // Note that PredicateResult.True() is just a shortcut for "new ValueTask<bool>(true)".
    ShouldHandle = static args => args.Outcome switch
    {
        { Exception: SomeExceptionType } => PredicateResult.True(),
        { Result: { StatusCode: HttpStatusCode.InternalServerError } } => PredicateResult.True(),
        _ => PredicateResult.False()
    },
    MaxRetryAttempts = 3,
})
.Build();
Tip

Things to remember:

  • Use AddRetry to add a retry strategy to your resiliency pipeline
  • Use the RetryStrategyOptions{<TResult>} to customize your retry behavior to meet your requirements

For further information please check out the Retry resilience strategy documentation.

Migrating rate limit policies

The rate limit policy is now replaced by the rate limiter strategy which uses the System.Threading.RateLimiting package. Polly does not implement its own rate limiter anymore.

Rate limit in v7

// Create sync rate limiter
ISyncPolicy syncPolicy = Policy.RateLimit(
    numberOfExecutions: 100,
    perTimeSpan: TimeSpan.FromMinutes(1));

// Create async rate limiter
IAsyncPolicy asyncPolicy = Policy.RateLimitAsync(
    numberOfExecutions: 100,
    perTimeSpan: TimeSpan.FromMinutes(1));

// Create generic sync rate limiter
ISyncPolicy<HttpResponseMessage> syncPolicyT = Policy.RateLimit<HttpResponseMessage>(
    numberOfExecutions: 100,
    perTimeSpan: TimeSpan.FromMinutes(1));

// Create generic async rate limiter
IAsyncPolicy<HttpResponseMessage> asyncPolicyT = Policy.RateLimitAsync<HttpResponseMessage>(
    numberOfExecutions: 100,
    perTimeSpan: TimeSpan.FromMinutes(1));

Rate limit in v8

Note

In v8, you have to add the Polly.RateLimiting package to your application otherwise you won't see the AddRateLimiter extension.

// The equivalent to Polly v7's RateLimit is the SlidingWindowRateLimiter.
//
// Polly exposes just a simple wrapper to the APIs exposed by the System.Threading.RateLimiting APIs.
// There is no need to create separate instances for sync and async flows as ResiliencePipeline handles both scenarios.
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
    .AddRateLimiter(new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions
    {
        PermitLimit = 100,
        Window = TimeSpan.FromMinutes(1),
    }))
    .Build();

// The creation of generic pipeline is almost identical.
//
// Polly exposes the same set of rate-limiter extensions for both ResiliencePipeline<HttpResponseMessage> and ResiliencePipeline.
ResiliencePipeline<HttpResponseMessage> pipelineT = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddRateLimiter(new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions
    {
        PermitLimit = 100,
        Window = TimeSpan.FromMinutes(1),
    }))
    .Build();
Tip

Things to remember:

  • Use AddRateLimiter to add a rate limiter strategy to your resiliency pipeline
  • Use one of the derived classes of ReplenishingRateLimiter to customize your rate limiter behavior to meet your requirements

For further information please check out the Rate limiter resilience strategy documentation.

Migrating bulkhead policies

The bulkhead policy is now replaced by the rate limiter strategy which uses the System.Threading.RateLimiting package. The new counterpart to bulkhead is ConcurrencyLimiter.

Note

In v7, the bulkhead was presented as an individual strategy. In v8, it's not separately exposed because it's essentially a specialized type of rate limiter: the ConcurrencyLimiter.

Bulkhead in v7

// Create sync bulkhead
ISyncPolicy syncPolicy = Policy.Bulkhead(
    maxParallelization: 100,
    maxQueuingActions: 50);

// Create async bulkhead
IAsyncPolicy asyncPolicy = Policy.BulkheadAsync(
    maxParallelization: 100,
    maxQueuingActions: 50);

// Create generic sync bulkhead
ISyncPolicy<HttpResponseMessage> syncPolicyT = Policy.Bulkhead<HttpResponseMessage>(
    maxParallelization: 100,
    maxQueuingActions: 50);

// Create generic async bulkhead
IAsyncPolicy<HttpResponseMessage> asyncPolicyT = Policy.BulkheadAsync<HttpResponseMessage>(
    maxParallelization: 100,
    maxQueuingActions: 50);

Bulkhead in v8

Note

In v8, you have to add the Polly.RateLimiting package to your application otherwise you won't see the AddConcurrencyLimiter extension.

// Create pipeline with concurrency limiter. Because ResiliencePipeline supports both sync and async
// callbacks, there is no need to define it twice.
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
    .AddConcurrencyLimiter(permitLimit: 100, queueLimit: 50)
    .Build();

// Create a generic pipeline with concurrency limiter. Because ResiliencePipeline<T> supports both sync and async
// callbacks, there is no need to define it twice.
ResiliencePipeline<HttpResponseMessage> pipelineT = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddConcurrencyLimiter(permitLimit: 100, queueLimit: 50)
    .Build();
Tip

Things to remember:

  • Use AddConcurrencyLimiter to add a concurrency limiter strategy to your resiliency pipeline
  • Use the ConcurrencyLimiterOptions to customize your concurrency limiter behavior to meet your requirements

For further information please check out the Rate limiter resilience strategy documentation.

Migrating timeout policies

Note

In v8, the timeout resilience strategy does not support pessimistic timeouts because they can cause thread-pool starvation and non-cancellable background tasks. To address this, you can use this workaround to make the action cancellable.

Timeout in v7

// Create sync timeout
ISyncPolicy syncPolicy = Policy.Timeout(TimeSpan.FromSeconds(10));

// Create async timeout
IAsyncPolicy asyncPolicy = Policy.TimeoutAsync(TimeSpan.FromSeconds(10));

// Create generic sync timeout
ISyncPolicy<HttpResponseMessage> syncPolicyT = Policy.Timeout<HttpResponseMessage>(TimeSpan.FromSeconds(10));

// Create generic async timeout
IAsyncPolicy<HttpResponseMessage> asyncPolicyT = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10));

Timeout in v8

// Create pipeline with timeout. Because ResiliencePipeline supports both sync and async
// callbacks, there is no need to define it twice.
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
    .AddTimeout(TimeSpan.FromSeconds(10))
    .Build();

// Create a generic pipeline with timeout. Because ResiliencePipeline<T> supports both sync and async
// callbacks, there is no need to define it twice.
ResiliencePipeline<HttpResponseMessage> pipelineT = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddTimeout(TimeSpan.FromSeconds(10))
    .Build();
Tip

Things to remember:

  • Use AddTimeout to add a timeout strategy to your resiliency pipeline
  • Use the TimeoutStrategyOptions to customize your timeout behavior to meet your requirements

For further information please check out the Timeout resilience strategy documentation.

Migrating circuit breaker policies

This section describes how to migrate v7 circuit breaker policies to V8 circuit breaker strategies.

Circuit breaker in v7

V7's "Standard" Circuit Breaker policy could be defined like below:

// Create sync circuit breaker
ISyncPolicy syncPolicy = Policy
    .Handle<SomeExceptionType>()
    .CircuitBreaker(
        exceptionsAllowedBeforeBreaking: 2,
        durationOfBreak: TimeSpan.FromSeconds(1));

// Create async circuit breaker
IAsyncPolicy asyncPolicy = Policy
    .Handle<SomeExceptionType>()
    .CircuitBreakerAsync(
        exceptionsAllowedBeforeBreaking: 2,
        durationOfBreak: TimeSpan.FromSeconds(1));

// Create generic sync circuit breaker
ISyncPolicy<HttpResponseMessage> syncPolicyT = Policy<HttpResponseMessage>
    .Handle<SomeExceptionType>()
    .CircuitBreaker(
        handledEventsAllowedBeforeBreaking: 2,
        durationOfBreak: TimeSpan.FromSeconds(1));

// Create generic async circuit breaker
IAsyncPolicy<HttpResponseMessage> asyncPolicyT = Policy<HttpResponseMessage>
    .Handle<SomeExceptionType>()
    .CircuitBreakerAsync(
        handledEventsAllowedBeforeBreaking: 2,
        durationOfBreak: TimeSpan.FromSeconds(1));

V7's Advanced Circuit Breaker policy could be defined like below:

// Create sync advanced circuit breaker
ISyncPolicy syncPolicy = Policy
    .Handle<SomeExceptionType>()
    .AdvancedCircuitBreaker(
        failureThreshold: 0.5d,
        samplingDuration: TimeSpan.FromSeconds(5),
        minimumThroughput: 2,
        durationOfBreak: TimeSpan.FromSeconds(1));

// Create async advanced circuit breaker
IAsyncPolicy asyncPolicy = Policy
    .Handle<SomeExceptionType>()
    .AdvancedCircuitBreakerAsync(
        failureThreshold: 0.5d,
        samplingDuration: TimeSpan.FromSeconds(5),
        minimumThroughput: 2,
        durationOfBreak: TimeSpan.FromSeconds(1));

// Create generic sync advanced circuit breaker
ISyncPolicy<HttpResponseMessage> syncPolicyT = Policy<HttpResponseMessage>
    .Handle<SomeExceptionType>()
    .AdvancedCircuitBreaker(
        failureThreshold: 0.5d,
        samplingDuration: TimeSpan.FromSeconds(5),
        minimumThroughput: 2,
        durationOfBreak: TimeSpan.FromSeconds(1));

// Create generic async advanced circuit breaker
IAsyncPolicy<HttpResponseMessage> asyncPolicyT = Policy<HttpResponseMessage>
    .Handle<SomeExceptionType>()
    .AdvancedCircuitBreakerAsync(
        failureThreshold: 0.5d,
        samplingDuration: TimeSpan.FromSeconds(5),
        minimumThroughput: 2,
        durationOfBreak: TimeSpan.FromSeconds(1));

// Check circuit state
ICircuitBreakerPolicy cbPolicy = (ICircuitBreakerPolicy)asyncPolicy;
bool isOpen = cbPolicy.CircuitState == CircuitState.Open || cbPolicy.CircuitState == CircuitState.Isolated;

// Manually control state
cbPolicy.Isolate(); // Transitions into the Isolated state
cbPolicy.Reset(); // Transitions into the Closed state

Circuit breaker in v8

Note

Polly V8 does not support the standard ("classic") circuit breaker with consecutive failure counting.

In case of V8 you can define a Circuit Breaker strategy which works like the advanced circuit breaker in V7.

// Create pipeline with circuit breaker. Because ResiliencePipeline supports both sync and async
// callbacks, there is no need to define it twice.
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
    .AddCircuitBreaker(new CircuitBreakerStrategyOptions
    {
        ShouldHandle = new PredicateBuilder().Handle<SomeExceptionType>(),
        FailureRatio = 0.5d,
        SamplingDuration = TimeSpan.FromSeconds(5),
        MinimumThroughput = 2,
        BreakDuration = TimeSpan.FromSeconds(1)
    })
    .Build();

// Create a generic pipeline with circuit breaker. Because ResiliencePipeline<T> supports both sync and async
// callbacks, there is also no need to define it twice.
ResiliencePipeline<HttpResponseMessage> pipelineT = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddCircuitBreaker(new CircuitBreakerStrategyOptions<HttpResponseMessage>
    {
        ShouldHandle = new PredicateBuilder<HttpResponseMessage>().Handle<SomeExceptionType>(),
        FailureRatio = 0.5d,
        SamplingDuration = TimeSpan.FromSeconds(5),
        MinimumThroughput = 2,
        BreakDuration = TimeSpan.FromSeconds(1)
    })
    .Build();

// Check circuit state
CircuitBreakerStateProvider stateProvider = new();
// Manually control state
CircuitBreakerManualControl manualControl = new();

ResiliencePipeline pipelineState = new ResiliencePipelineBuilder()
    .AddCircuitBreaker(new CircuitBreakerStrategyOptions
    {
        ShouldHandle = new PredicateBuilder().Handle<SomeExceptionType>(),
        FailureRatio = 0.5d,
        SamplingDuration = TimeSpan.FromSeconds(5),
        MinimumThroughput = 2,
        BreakDuration = TimeSpan.FromSeconds(1),
        StateProvider = stateProvider,
        ManualControl = manualControl
    })
    .Build();

// Check circuit state
bool isOpen = stateProvider.CircuitState == CircuitState.Open || stateProvider.CircuitState == CircuitState.Isolated;

// Manually control state
await manualControl.IsolateAsync(); // Transitions into the Isolated state
await manualControl.CloseAsync(); // Transitions into the Closed state
Note

In case of V7 you could do an optimization to reduce the thrown exceptions.

You could guard the Execute{Async} call with a condition that the circuit is not broken. This technique does not work with V8.

Under the circuit breaker's anti-patterns you can find the suggested way for V8.


Tip

Things to remember:

  • Use AddCircuitBreaker to add a circuit breaker strategy to your resiliency pipeline
  • Use the CircuitBreakerStrategyOptions{<TResult>} to customize your circuit breaker behavior to meet your requirements

For further information please check out the Circuit Breaker resilience strategy documentation.

Migrating Polly.Context

The successor of the Polly.Context is the ResilienceContext. The major differences:

  • ResilienceContext is pooled for enhanced performance and cannot be directly created. Instead, use the ResilienceContextPool class to get an instance.
  • Context allowed directly custom data attachment, whereas ResilienceContext employs the ResilienceContext.Properties for the same purpose.
  • In order to set or get a custom data you need to utilize the generic ResiliencePropertyKey structure.

Predefined keys

In V7 In V8
OperationKey It can be used in the same way
PolicyKey It's been relocated to ResiliencePipelineBuilder and used for telemetry
PolicyWrapKey It's been relocated to ResiliencePipelineBuilder and used for telemetry
CorrelationId It's been removed. For similar functionality, you can either use System.Diagnostics.Activity.Current.Id or attach your custom Id using ResilienceContext.Properties.
  • Additionally, ResilienceContext introduces a new property for CancellationToken.

Context in v7

// Create context
Context context = new Context();

// Create context with operation key
context = new Context("my-operation-key");

// Attach custom properties
context[Key1] = "value-1";
context[Key2] = 100;

// Retrieve custom properties
string value1 = (string)context[Key1];
int value2 = (int)context[Key2];

// Bulk attach
context = new Context("my-operation-key", new Dictionary<string, object>
{
    { Key1 , "value-1" },
    { Key2 , 100 }
});

ResilienceContext in v8

// Create context
ResilienceContext context = ResilienceContextPool.Shared.Get();

// Create context with operation key
context = ResilienceContextPool.Shared.Get("my-operation-key");

// Attach custom properties
ResiliencePropertyKey<string> propertyKey1 = new(Key1);
context.Properties.Set(propertyKey1, "value-1");

ResiliencePropertyKey<int> propertyKey2 = new(Key2);
context.Properties.Set(propertyKey2, 100);

// Bulk attach
context.Properties.SetProperties(new Dictionary<string, object?>
{
    { Key1 , "value-1" },
    { Key2 , 100 }
}, out var oldProperties);

// Retrieve custom properties
string value1 = context.Properties.GetValue(propertyKey1, "default");
int value2 = context.Properties.GetValue(propertyKey2, 0);

// Return the context to the pool
ResilienceContextPool.Shared.Return(context);
Tip

Things to remember:

  • Use ResilienceContextPool.Shared to get a context and return it back to the pool
  • Use the ResiliencePropertyKey<TValue> to define type-safe keys for your custom data

For further information please check out the Resilience Context documentation.

Migrating safe execution

In v7, the ExecuteAndCapture{Async} methods are considered the safe counterpart of the Execute{Async}.

The former does not throw an exception in case of failure rather than wrap the outcome in a result object.

In v8, the ExecuteOutcomeAsync method should be used to execute the to-be-decorated method in a safe way.

ExecuteAndCapture{Async} in V7

// Synchronous execution
ISyncPolicy<int> syncPolicy = Policy.Timeout<int>(TimeSpan.FromSeconds(1));
PolicyResult<int> policyResult = syncPolicy.ExecuteAndCapture(Method);

// Asynchronous execution
IAsyncPolicy<int> asyncPolicy = Policy.TimeoutAsync<int>(TimeSpan.FromSeconds(1));
PolicyResult<int> asyncPolicyResult = await asyncPolicy.ExecuteAndCaptureAsync(MethodAsync, CancellationToken.None);

// Assess policy result
if (policyResult.Outcome == OutcomeType.Successful)
{
    int result = policyResult.Result;

    // Process result
}
else
{
    Exception exception = policyResult.FinalException;
    FaultType faultType = policyResult.FaultType!.Value;
    ExceptionType exceptionType = policyResult.ExceptionType!.Value;

    // Process failure
}

// Access context
const string Key = "context_key";
IAsyncPolicy<int> asyncPolicyWithContext = Policy.TimeoutAsync<int>(TimeSpan.FromSeconds(10),
    onTimeoutAsync: (ctx, ts, task) =>
    {
        ctx[Key] = "context_value";
        return Task.CompletedTask;
    });

asyncPolicyResult = await asyncPolicyWithContext.ExecuteAndCaptureAsync((ctx, token) => MethodAsync(token), new Context(), CancellationToken.None);
string? ctxValue = asyncPolicyResult.Context.GetValueOrDefault(Key) as string;

ExecuteOutcomeAsync in V8

Note

Polly V8 does not provide an API to synchronously execute and capture the outcome of a pipeline.

ResiliencePipeline<int> pipeline = new ResiliencePipelineBuilder<int>()
    .AddTimeout(TimeSpan.FromSeconds(1))
    .Build();

// Synchronous execution
// Polly v8 does not support

// Asynchronous execution
var context = ResilienceContextPool.Shared.Get();
Outcome<int> pipelineResult = await pipeline.ExecuteOutcomeAsync(
    static async (ctx, state) => Outcome.FromResult(await MethodAsync(ctx.CancellationToken)), context, "state");
ResilienceContextPool.Shared.Return(context);

// Assess policy result
if (pipelineResult.Exception is null)
{
    int result = pipelineResult.Result;

    // Process result
}
else
{
    Exception exception = pipelineResult.Exception;

    // Process failure

    // If needed you can rethrow the exception
    pipelineResult.ThrowIfException();
}

// Access context
ResiliencePropertyKey<string> contextKey = new("context_key");
ResiliencePipeline<int> pipelineWithContext = new ResiliencePipelineBuilder<int>()
    .AddTimeout(new TimeoutStrategyOptions
    {
        Timeout = TimeSpan.FromSeconds(1),
        OnTimeout = args =>
        {
            args.Context.Properties.Set(contextKey, "context_value");
            return default;
        }
    })
    .Build();

context = ResilienceContextPool.Shared.Get();
pipelineResult = await pipelineWithContext.ExecuteOutcomeAsync(
    static async (ctx, state) => Outcome.FromResult(await MethodAsync(ctx.CancellationToken)), context, "state");

context.Properties.TryGetValue(contextKey, out var ctxValue);
ResilienceContextPool.Shared.Return(context);
Tip

Things to remember:

  • Use ExecuteOutcomeAsync to execute your callback in a safe way

Migrating no-op policies

In V7 In V8
Policy.NoOp ResiliencePipeline.Empty
Policy.NoOpAsync ResiliencePipeline.Empty
Policy.NoOp<TResult> ResiliencePipeline<TResult>.Empty
Policy.NoOpAsync<TResult> ResiliencePipeline<TResult>.Empty

Migrating policy registries

In v7, the following registry APIs are exposed:

  • IConcurrentPolicyRegistry<TKey>
  • IPolicyRegistry<TKey>
  • IReadOnlyPolicyRegistry<TKey>
  • PolicyRegistry<TKey>

In v8, these have been replaced by:

  • ResiliencePipelineProvider<TKey>: Allows adding and accessing resilience pipelines.
  • ResiliencePipelineRegistry<TKey>: Read-only access to resilience pipelines.

The main updates:

  • It's append-only, which means removal of items is not supported to avoid race conditions.
  • It's thread-safe and supports features like dynamic reloading and resource disposal.
  • It allows dynamic creation and caching of resilience pipelines using pre-registered delegates.
  • Type safety is enhanced, eliminating the need for casting between policy types.

Registry in v7

// Create a registry
var registry = new PolicyRegistry();

// Add a policy
registry.Add(PolicyKey, Policy.Timeout(TimeSpan.FromSeconds(10)));

// Try get a policy
registry.TryGet<IAsyncPolicy>(PolicyKey, out IAsyncPolicy? policy);

// Try get a generic policy
registry.TryGet<IAsyncPolicy<string>>(PolicyKey, out IAsyncPolicy<string>? genericPolicy);

// Update a policy
registry.AddOrUpdate(
    PolicyKey,
    Policy.Timeout(TimeSpan.FromSeconds(10)),
    (key, previous) => Policy.Timeout(TimeSpan.FromSeconds(10)));

Registry in v8

Note

Polly V8 does not provide an explicit API to directly update a strategy in the registry.

On the other hand it does provide a mechanism to reload pipelines.

// Create a registry
var registry = new ResiliencePipelineRegistry<string>();

// Add a pipeline using a builder, when the pipeline is retrieved it will be dynamically built and cached
registry.TryAddBuilder(PipelineKey, (builder, context) => builder.AddTimeout(TimeSpan.FromSeconds(10)));

// Try get a pipeline
registry.TryGetPipeline(PipelineKey, out ResiliencePipeline? pipeline);

// Try get a generic pipeline
registry.TryGetPipeline<string>(PipelineKey, out ResiliencePipeline<string>? genericPipeline);

// Get or add pipeline
registry.GetOrAddPipeline(PipelineKey, builder => builder.AddTimeout(TimeSpan.FromSeconds(10)));
Tip

Things to remember:

  • Use ResiliencePipelineRegistry<TResult> to add or get a pipelines to the registry
  • Prefer the safer methods (for example: TryGetPipeline{<TResult>}) over their counterpart (for example: GetPipeline{<TResult>})

For further information please check out the Resilience pipeline registry documentation.

Interoperability between policies and resilience pipelines

In certain scenarios, you might not able to migrate all your code to the v8 API.

In the name of interoperability you can define V8 strategies use them with your v7 policies.

V8 provides a set of extension methods to support easy conversion from v8 to v7 APIs, as shown in the example below:

Note

In v8, you have to add the Polly.RateLimiting package to your application otherwise you won't see the AddRateLimiter extension.

// First, create a resilience pipeline.
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
    .AddRateLimiter(new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions
    {
        Window = TimeSpan.FromSeconds(10),
        PermitLimit = 100
    }))
    .Build();

// Now, convert it to a v7 policy. Note that it can be converted to both sync and async policies.
ISyncPolicy syncPolicy = pipeline.AsSyncPolicy();
IAsyncPolicy asyncPolicy = pipeline.AsAsyncPolicy();

// Finally, use it in a policy wrap.
ISyncPolicy wrappedPolicy = Policy.Wrap(
    syncPolicy,
    Policy.Handle<SomeExceptionType>().Retry(3));