# Polly
Polly is a .NET resilience and transient-fault-handling library that allows developers to express resilience strategies such as Retry, Circuit Breaker, Hedging, Timeout, Rate Limiter and Fallback in a fluent and thread-safe manner.
[
](https://www.dotnetfoundation.org/)
We are a member of the [.NET Foundation](https://www.dotnetfoundation.org/about)!
**Keep up to date with new feature announcements, tips & tricks, and other news through [www.thepollyproject.org](https://www.thepollyproject.org)**
[](https://github.com/App-vNext/Polly/actions?query=workflow%3Abuild+branch%3Amain+event%3Apush) [](https://codecov.io/gh/App-vNext/Polly)

> [!NOTE]
> This README aims to give a quick overview of all 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 [Polly documentation](docs/README.md).
> [!IMPORTANT]
> This documentation describes the new Polly v8 API. If you are using the v7 API, please refer to the [previous version](https://github.com/App-vNext/Polly/tree/7.2.4) of the documentation.
## NuGet Packages
| **Package** | **Latest Version** |
|:--|:--|
| Polly | [](https://www.nuget.org/packages/Polly/ "Download Polly from NuGet.org") |
| Polly.Core | [](https://www.nuget.org/packages/Polly.Core/ "Download Polly.Core from NuGet.org") |
| Polly.Extensions | [](https://www.nuget.org/packages/Polly.Extensions/ "Download Polly.Extensions from NuGet.org") |
| Polly.RateLimiting | [](https://www.nuget.org/packages/Polly.RateLimiting/ "Download Polly.RateLimiting from NuGet.org") |
| Polly.Testing | [](https://www.nuget.org/packages/Polly.Testing/ "Download Polly.Testing from NuGet.org") |
## Quick start
To use Polly, you must provide a callback and execute it using [**resilience pipeline**](docs/resilience-pipelines.md). A resilience pipeline is a combination of one or more [**resilience strategies**](docs/resilience-strategies.md) such as retry, timeout, and rate limiter. Polly uses **builders** to integrate these strategies into a pipeline.
To get started, first add the [Polly.Core](https://www.nuget.org/packages/Polly.Core/) package to your project by running the following command:
```sh
dotnet add package Polly.Core
```
You can create a `ResiliencePipeline` using the `ResiliencePipelineBuilder` class as shown below:
```cs
// Create a instance of builder that exposes various extensions for adding resilience strategies
var builder = new ResiliencePipelineBuilder();
// Add retry using the default options
builder.AddRetry(new RetryStrategyOptions());
// Add 10 second timeout
builder.AddTimeout(TimeSpan.FromSeconds(10));
// Build the resilience pipeline
ResiliencePipeline pipeline = builder.Build();
// Execute the pipeline
await pipeline.ExecuteAsync(async token =>
{
// Your custom logic here
});
```
### Dependency injection
If you prefer to define resilience pipelines using [`IServiceCollection`](https://learn.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.iservicecollection), you'll need to install the [Polly.Extensions](https://www.nuget.org/packages/Polly.Extensions/) package:
```sh
dotnet add package Polly.Extensions
```
You can then define your resilience pipeline using the `AddResiliencePipeline(...)` extension method as shown:
```cs
var services = new ServiceCollection();
// Define a resilience pipeline with the name "my-pipeline"
services.AddResiliencePipeline("my-pipeline", builder =>
{
builder
.AddRetry(new RetryStrategyOptions())
.AddTimeout(TimeSpan.FromSeconds(10));
});
// Build the service provider
IServiceProvider serviceProvider = services.BuildServiceProvider();
// Retrieve ResiliencePipelineProvider that caches and dynamically creates the resilience pipelines
var pipelineProvider = serviceProvider.GetRequiredService>();
// Retrieve resilience pipeline using the name it was registered with
ResiliencePipeline pipeline = pipelineProvider.GetPipeline("my-pipeline");
// Execute the pipeline
await pipeline.ExecuteAsync(async token =>
{
// Your custom logic here
});
```
## Resilience strategies
Polly provides a variety of resilience strategies. Alongside the comprehensive guides for each strategy, the wiki also includes an [overview of the role each strategy plays in resilience engineering](https://github.com/App-vNext/Polly/wiki/Transient-fault-handling-and-proactive-resilience-engineering).
Polly categorizes resilience strategies into two main groups:
- **Reactive**: These strategies handle specific exceptions that are thrown, or results that are returned, by the callbacks executed through the strategy.
- **Proactive**: Unlike reactive strategies, proactive strategies do not focus on handling errors by the callbacks might throw or return. They can make pro-active decisions to cancel or reject the execution of callbacks (e.g., using a rate limiter or a timeout resilience strategy).
| Strategy | Reactive | Premise | AKA | How does the strategy mitigate?|
| ------------- | --- | ------------- |:-------------: |------------- |
|**Retry**
(strategy family)
([quickstart](#retry) ; [deep](https://github.com/App-vNext/Polly/wiki/Retry)) |Yes|Many faults are transient and may self-correct after a short delay.| *Maybe it's just a blip* | Allows configuring automatic retries. |
|**Circuit-breaker**
(strategy family)
([quickstart](#circuit-breaker) ; [deep](https://github.com/App-vNext/Polly/wiki/Circuit-Breaker))|Yes|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. |
|**Timeout**
([quickstart](#timeout) ; [deep](https://github.com/App-vNext/Polly/wiki/Timeout))|No|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**
([quickstart](#rate-limiter) ; [deep](https://github.com/App-vNext/Polly/wiki/Rate-Limit))|No|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. |
|**Fallback**
([quickstart](#fallback) ; [deep](https://github.com/App-vNext/Polly/wiki/Fallback))|Yes|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**
([quickstart](#hedging) ; [deep](https://github.com/App-vNext/Polly/wiki/TODO))|Yes|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. |
Visit [resilience strategies](docs/resilience-strategies.md) docs to explore how to configure individual resilience strategies in more detail.
### Retry
```cs
// Add retry using the default options
new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions());
// For instant retries with no delay
new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions
{
Delay = TimeSpan.Zero
});
// For advanced control over the retry behavior, including the number of attempts,
// delay between retries, and the types of exceptions to handle.
new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions
{
ShouldHandle = new PredicateBuilder().Handle(),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true, // Adds a random factor to the delay
MaxRetryAttempts = 4,
Delay = TimeSpan.FromSeconds(3),
});
// To use a custom function to generate the delay for retries
new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 2,
DelayGenerator = args =>
{
var delay = args.AttemptNumber switch
{
0 => TimeSpan.Zero,
1 => TimeSpan.FromSeconds(1),
_ => TimeSpan.FromSeconds(5)
};
// This example uses a synchronous delay generator,
// but the API also supports asynchronous implementations.
return new ValueTask(delay);
}
});
// To extract the delay from the result object
new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions
{
DelayGenerator = args =>
{
if (args.Outcome.Result is HttpResponseMessage responseMessage &&
TryGetDelay(responseMessage, out TimeSpan delay))
{
return new ValueTask(delay);
}
// Returning null means the retry strategy will use its internal delay for this attempt.
return new ValueTask((TimeSpan?)null);
}
});
// To get notifications when a retry is performed
new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 2,
OnRetry = args =>
{
Console.WriteLine("OnRetry, Attempt: {0}", args.AttemptNumber);
// Event handlers can be asynchronous; here, we return an empty ValueTask.
return default;
}
});
// To keep retrying indefinitely until successful
new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = int.MaxValue,
});
```
If all retries fail, a retry strategy rethrows the final exception back to the calling code. For more details visit the [retry strategy documentation](https://github.com/App-vNext/Polly/wiki/Retry).
### Circuit Breaker
```cs
// Add circuit breaker with default options.
new ResiliencePipelineBuilder().AddCircuitBreaker(new CircuitBreakerStrategyOptions());
// Add circuit breaker with customized options:
//
// The circuit will break if more than 50% of actions result in handled exceptions,
// within any 10-second sampling duration, and at least 8 actions are processed.
new ResiliencePipelineBuilder().AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
SamplingDuration = TimeSpan.FromSeconds(10),
MinimumThroughput = 8,
BreakDuration = TimeSpan.FromSeconds(30),
ShouldHandle = new PredicateBuilder().Handle()
});
// Handle specific failed results for HttpResponseMessage:
new ResiliencePipelineBuilder()
.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
ShouldHandle = new PredicateBuilder()
.Handle()
.HandleResult(response => response.StatusCode == HttpStatusCode.InternalServerError)
});
// Monitor the circuit state, useful for health reporting:
var stateProvider = new CircuitBreakerStateProvider();
new ResiliencePipelineBuilder()
.AddCircuitBreaker(new() { StateProvider = stateProvider })
.Build();
/*
CircuitState.Closed - Normal operation; actions are executed.
CircuitState.Open - Circuit is open; actions are blocked.
CircuitState.HalfOpen - Recovery state after break duration expires; actions are permitted.
CircuitState.Isolated - Circuit is manually held open; actions are blocked.
*/
// Manually control the Circuit Breaker state:
var manualControl = new CircuitBreakerManualControl();
new ResiliencePipelineBuilder()
.AddCircuitBreaker(new() { ManualControl = manualControl })
.Build();
// Manually isolate a circuit, e.g., to isolate a downstream service.
await manualControl.IsolateAsync();
// Manually close the circuit to allow actions to be executed again.
await manualControl.CloseAsync();
```
The Circuit Breaker strategy prevents execution by throwing a `BrokenCircuitException` when the circuit is open. For more details, refer to the [Circuit-Breaker documentation on GitHub](https://github.com/App-vNext/Polly/wiki/Advanced-Circuit-Breaker).
> [!NOTE]
> Be aware that the Circuit Breaker strategy [rethrows all exceptions](https://github.com/App-vNext/Polly/wiki/Circuit-Breaker#exception-handling), including those that are handled. A Circuit Breaker's role is to monitor faults and break the circuit when a certain threshold is reached; it does not manage retries. Combine the Circuit Breaker with a Retry strategy if needed.
For more insights on the Circuit Breaker pattern, you can visit:
- [Making the Netflix API More Resilient](https://techblog.netflix.com/2011/12/making-netflix-api-more-resilient.html)
- [Circuit Breaker by Martin Fowler](https://martinfowler.com/bliki/CircuitBreaker.html)
- [Circuit Breaker Pattern by Microsoft](https://msdn.microsoft.com/en-us/library/dn589784.aspx)
- [Original Circuit Breaking Article](https://web.archive.org/web/20160106203951/http://thatextramile.be/blog/2008/05/the-circuit-breaker)
### Fallback
```cs
// Use a fallback/substitute value if an operation fails.
new ResiliencePipelineBuilder()
.AddFallback(new FallbackStrategyOptions
{
ShouldHandle = new PredicateBuilder()
.Handle()
.HandleResult(r => r is null),
FallbackAction = args => Outcome.FromResultAsValueTask(UserAvatar.Blank)
});
// Use a dynamically generated value if an operation fails.
new ResiliencePipelineBuilder()
.AddFallback(new FallbackStrategyOptions
{
ShouldHandle = new PredicateBuilder()
.Handle()
.HandleResult(r => r is null),
FallbackAction = args =>
{
var avatar = UserAvatar.GetRandomAvatar();
return Outcome.FromResultAsValueTask(avatar);
}
});
// Use a default or dynamically generated value, and execute an additional action if the fallback is triggered.
new ResiliencePipelineBuilder()
.AddFallback(new FallbackStrategyOptions
{
ShouldHandle = new PredicateBuilder()
.Handle()
.HandleResult(r => r is null),
FallbackAction = args =>
{
var avatar = UserAvatar.GetRandomAvatar();
return Outcome.FromResultAsValueTask(UserAvatar.Blank);
},
OnFallback = args =>
{
// Add extra logic to be executed when the fallback is triggered, such as logging.
return default; // returns an empty ValueTask
}
});
```
For more details, refer to the [Fallback documentation](https://github.com/App-vNext/Polly/wiki/Fallback).
### Hedging
```cs
// Add hedging with default options.
new ResiliencePipelineBuilder()
.AddHedging(new HedgingStrategyOptions());
// Add a customized hedging strategy that retries up to 3 times if the execution
// takes longer than 1 second or if it fails due to an exception or returns an HTTP 500 Internal Server Error.
new ResiliencePipelineBuilder()
.AddHedging(new HedgingStrategyOptions
{
ShouldHandle = new PredicateBuilder()
.Handle()
.HandleResult(response => response.StatusCode == HttpStatusCode.InternalServerError),
MaxHedgedAttempts = 3,
Delay = TimeSpan.FromSeconds(1),
ActionGenerator = args =>
{
Console.WriteLine("Preparing to execute hedged action.");
// Return a delegate function to invoke the original action with the action context.
// Optionally, you can also create a completely new action to be executed.
return () => args.Callback(args.ActionContext);
}
});
// Subscribe to hedging events.
new ResiliencePipelineBuilder()
.AddHedging(new HedgingStrategyOptions
{
OnHedging = args =>
{
Console.WriteLine($"OnHedging: Attempt number {args.AttemptNumber}");
return default;
}
});
```
If all hedged attempts fail, the hedging strategy will either re-throw the last exception or return the final failed result to the caller. For more information, refer to the [hedging strategy documentation](docs/hedging.md).
### Timeout
The timeout resilience strategy assumes delegates you execute support [co-operative cancellation](https://learn.microsoft.com/dotnet/standard/threading/cancellation-in-managed-threads). You must use `Execute/Async(...)` overloads taking a `CancellationToken`, and the executed delegate must honor that `CancellationToken`.
```cs
// To add timeout using the default options
new ResiliencePipelineBuilder()
.AddTimeout(new TimeoutStrategyOptions());
// To add a timeout with a custom TimeSpan duration
new ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(3));
// To add a timeout using a custom timeout generator function
new ResiliencePipelineBuilder()
.AddTimeout(new TimeoutStrategyOptions
{
TimeoutGenerator = args =>
{
// Note: the timeout generator supports asynchronous operations
return new ValueTask(TimeSpan.FromSeconds(123));
}
});
// To add a timeout and listen for timeout events
new ResiliencePipelineBuilder()
.AddTimeout(new TimeoutStrategyOptions
{
TimeoutGenerator = args =>
{
// Note: the timeout generator supports asynchronous operations
return new ValueTask(TimeSpan.FromSeconds(123));
},
OnTimeout = args =>
{
Console.WriteLine($"{args.Context.OperationKey}: Execution timed out after {args.Timeout.TotalSeconds} seconds.");
return default;
}
});
```
Example execution:
```cs
var pipeline = new ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(3))
.Build();
HttpResponseMessage httpResponse = await pipeline.ExecuteAsync(
async ct =>
{
// Execute a delegate that takes a CancellationToken as an input parameter.
return await httpClient.GetAsync(endpoint, ct);
},
cancellationToken);
```
Timeout strategies throw `TimeoutRejectedException` when a timeout occurs. For more details see [Timeout strategy documentation](https://github.com/App-vNext/Polly/wiki/Timeout).
### Rate Limiter
```cs
// Add rate limiter with default options.
new ResiliencePipelineBuilder()
.AddRateLimiter(new RateLimiterStrategyOptions());
// Create a rate limiter to allow a maximum of 100 concurrent executions and a queue of 50.
new ResiliencePipelineBuilder()
.AddConcurrencyLimiter(100, 50);
// Create a rate limiter that allows 100 executions per minute.
new ResiliencePipelineBuilder()
.AddRateLimiter(new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
}));
// Create a custom partitioned rate limiter.
var partitionedLimiter = PartitionedRateLimiter.Create(context =>
{
// Extract the partition key.
string partitionKey = GetPartitionKey(context);
return RateLimitPartition.GetConcurrencyLimiter(
partitionKey,
key => new ConcurrencyLimiterOptions
{
PermitLimit = 100
});
});
new ResiliencePipelineBuilder()
.AddRateLimiter(new RateLimiterStrategyOptions
{
// Provide a custom rate limiter delegate.
RateLimiter = args =>
{
return partitionedLimiter.AcquireAsync(args.Context, 1, args.Context.CancellationToken);
}
});
```
Example execution:
```cs
var pipeline = new ResiliencePipelineBuilder().AddConcurrencyLimiter(100, 50).Build();
try
{
// Execute an asynchronous text search operation.
var result = await pipeline.ExecuteAsync(
token => TextSearchAsync(query, token),
cancellationToken);
}
catch (RateLimiterRejectedException ex)
{
// Handle RateLimiterRejectedException,
// that can optionally contain information about when to retry.
if (ex.RetryAfter is TimeSpan retryAfter)
{
Console.WriteLine($"Retry After: {retryAfter}");
}
}
```
Rate limiter strategy throws `RateLimiterRejectedException` if execution is rejected. For more details see [Rate Limiter strategy documentation](https://github.com/App-vNext/Polly/wiki/Rate-Limit).
## Next steps
To learn more about Polly, visit the [documentation](docs/README.md) or check out the [samples](#samples).
## Samples
- [Samples](samples/README.md): Samples in this repository that serve as an introduction to Polly.
- [Polly-Samples](https://github.com/App-vNext/Polly-Samples): Contains practical examples for using various implementations of Polly. Please feel free to contribute to the Polly-Samples repository in order to assist others who are either learning Polly for the first time, or are seeking advanced examples and novel approaches provided by our generous community.
- Microsoft's [eShopOnContainers project](https://github.com/dotnet-architecture/eShopOnContainers): Sample project demonstrating a .NET Microservices architecture and using Polly for resilience.
## License
Licensed under the terms of the [New BSD License](http://opensource.org/licenses/BSD-3-Clause)