diff --git a/README.md b/README.md
index dbc9fd54cb..03d6395351 100644
--- a/README.md
+++ b/README.md
@@ -5,11 +5,9 @@
> Major performance improvements are on the way! Please see our [blog post](https://www.thepollyproject.org/2023/03/03/we-want-your-feedback-introducing-polly-v8/) to learn more and provide feedback in the [related GitHub issue](https://github.com/App-vNext/Polly/issues/1048).
>
> :rotating_light::rotating_light: **Polly v8 feature-complete!** :rotating_light::rotating_light:
-> - Polly v8 Beta 1 is now available on [NuGet.org](https://www.nuget.org/packages/Polly/8.0.0-beta.1)
+> - Polly v8 Beta 1 is now available on [NuGet.org](https://www.nuget.org/packages/Polly/8.0.0-beta.1).
> - The Beta 1 version is considered feature-complete and the public API surface is stable.
-> - The v8 docs are not yet finished, but you can take a look at sample code in these locations:
-> - Within the repo's new [Samples folder](https://github.com/App-vNext/Polly/tree/main/samples)
-> - By reading `Polly.Core`'s [README](https://github.com/App-vNext/Polly/blob/main/src/Polly.Core/README.md)
+> - Explore the [v8 documentation](README_V8.md).
# Polly
@@ -56,10 +54,6 @@ dotnet add package Polly
For details of supported compilation targets by version, see the [supported targets](https://github.com/App-vNext/Polly/wiki/Supported-targets) grid.
-### Using Polly with HttpClient factory from ASP.NET Core 2.1
-
-For using Polly with HttpClient factory from ASP.NET Core 2.1, see our [detailed wiki page](https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory), then come back here or [explore the wiki](https://github.com/App-vNext/Polly/wiki) to learn more about the operation of each policy.
-
### Role of the readme and the wiki
This ReadMe aims to give a quick overview of all Polly features - including enough to get you started with any policy. For deeper detail on any policy, and many other aspects of Polly, be sure also to check out the [wiki documentation](https://github.com/App-vNext/Polly/wiki).
@@ -1065,8 +1059,4 @@ Licensed under the terms of the [New BSD License](http://opensource.org/licenses
## Resources
-- [Extensibility](docs/v7/extensibility.md): Learn how you can extend Polly with new policies.
-- [Polly-Contrib](docs/polly-contrib.md): Learn how you can contribute to and enhance the Polly ecosystem.
-- [Simmy](docs/simmy.md): Understand chaos engineering through the use of Polly.
-- [3rd Party Libraries and Contributions](docs/libraries-and-contributions.md): Discover the libraries that Polly relies on and the contributors who help improve it.
-- [Blogs, Podcasts, Courses, E-books, etc.](docs/resources.md): Explore additional community resources.
+Visit the [documentation](docs/README.md) to explore more Polly-related resources.
diff --git a/README_V8.md b/README_V8.md
new file mode 100644
index 0000000000..90b2c9d2c7
--- /dev/null
+++ b/README_V8.md
@@ -0,0 +1,517 @@
+# 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)**
+
+[![Build status](https://github.com/App-vNext/Polly/workflows/build/badge.svg?branch=main&event=push)](https://github.com/App-vNext/Polly/actions?query=workflow%3Abuild+branch%3Amain+event%3Apush) [![Code coverage](https://codecov.io/gh/App-vNext/Polly/branch/main/graph/badge.svg)](https://codecov.io/gh/App-vNext/Polly)
+
+![Polly logo](https://raw.github.com/App-vNext/Polly/main/Polly-Logo.png)
+
+> [!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 | [![NuGet](https://buildstats.info/nuget/Polly?includePreReleases=true)](https://www.nuget.org/packages/Polly/ "Download Polly from NuGet.org") |
+| Polly.Core | [![NuGet](https://buildstats.info/nuget/Polly.Core?includePreReleases=true)](https://www.nuget.org/packages/Polly.Core/ "Download Polly.Core from NuGet.org") |
+| Polly.Extensions | [![NuGet](https://buildstats.info/nuget/Polly.Extensions?includePreReleases=true)](https://www.nuget.org/packages/Polly.Extensions/ "Download Polly.Extensions from NuGet.org") |
+| Polly.RateLimiting | [![NuGet](https://buildstats.info/nuget/Polly.RateLimiting?includePreReleases=true)](https://www.nuget.org/packages/Polly.RateLimiting/ "Download Polly.RateLimiting from NuGet.org") |
+| Polly.Testing | [![NuGet](https://buildstats.info/nuget/Polly.Testing?includePreReleases=true)](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)
diff --git a/docs/README.md b/docs/README.md
index a3daacc109..6b04987065 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -2,10 +2,22 @@
If you're already familiar with the [basic features](../README.md) of Polly, delve deeper into its advanced functionalities here.
-## Table of Contents
+## Topics
-- [Extensibility](v7/extensibility.md): Learn how you can extend Polly with new policies.
+- [General](general.md): General information about Polly.
+- [Resilience Pipelines](resilience-pipelines.md): Understanding the use of resilience pipelines.
+- [Resilience Strategies](resilience-strategies.md): General information about resilience strategies and how to configure them.
+- [Resilience Context](resilience-context.md): Describes the resilience context used by resilience pipelines and strategies.
+- [Resilience Pipeline Registry](resilience-pipeline-registry.md): Exploring the registry that stores resilience pipelines.
+- [Dependency Injection](dependency-injection.md): How Polly integrates with Dependency Injection.
+- [Telemetry](telemetry.md): Insights into telemetry generated by resilience pipelines and strategies.
+- [Extensibility](v7/extensibility.md): Learn how you can extend Polly with new resilience strategies.
- [Polly-Contrib](polly-contrib.md): Learn how to contribute to and extend the Polly ecosystem.
- [Simmy](simmy.md): Get to know chaos engineering via Polly's capabilities.
- [Third-Party Libraries and Contributions](libraries-and-contributions.md): Find out which libraries Polly depends on and who contributes to its development.
- [Additional Resources](resources.md): Browse through blogs, podcasts, courses, e-books, and other community resources.
+- [Using Polly with HttpClientFactory](https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory): For using Polly with HttpClientFactory in ASP.NET Core 2.1 and later versions.
+
+## Topics (previous Polly versions)
+
+- [Extensibility (v7)](v7/extensibility.md): Learn how you can extend Polly with new policies.
diff --git a/docs/dependency-injection.md b/docs/dependency-injection.md
new file mode 100644
index 0000000000..829ed5afae
--- /dev/null
+++ b/docs/dependency-injection.md
@@ -0,0 +1,3 @@
+# Dependency Injection
+
+🚧 This documentation is being written as part of the Polly v8 release.
diff --git a/docs/extensiblity.md b/docs/extensiblity.md
new file mode 100644
index 0000000000..62ac8e1209
--- /dev/null
+++ b/docs/extensiblity.md
@@ -0,0 +1,3 @@
+# Extensibility
+
+🚧 This documentation is being written as part of the Polly v8 release.
diff --git a/docs/general.md b/docs/general.md
new file mode 100644
index 0000000000..6a243973a8
--- /dev/null
+++ b/docs/general.md
@@ -0,0 +1,71 @@
+# General
+
+> [!NOTE]
+> This is documentation for the upcoming Polly v8 release.
+
+## Supported targets
+
+Polly targets .NET Standard 2.0+ ([coverage](https://docs.microsoft.com/dotnet/standard/net-standard#net-implementation-support): .NET Core 2.0+, .NET Core 3.0, .NET 6.0+ and later Mono, Xamarin and UWP targets). The NuGet package also includes direct targets for .NET Framework 4.6.1 and 4.7.2.
+
+For details of supported compilation targets by version, see the [supported targets](https://github.com/App-vNext/Polly/wiki/Supported-targets) grid.
+
+## Asynchronous support
+
+Polly provides native support for asynchronous operations through all its resilience strategies by offering the `ExecuteAsync` methods on the `ResiliencePipeline` class.
+
+### SynchronizationContext
+
+By default, asynchronous continuations and retries do not execute on a captured synchronization context. To modify this behavior, you can use the `ResilienceContext` class and set its `ContinueOnCapturedContext` property to `true`. The following example illustrates this:
+
+
+```cs
+// Retrieve an instance of ResilienceContext from the pool
+// with the ContinueOnCapturedContext property set to true
+ResilienceContext context = ResilienceContextPool.Shared.Get(continueOnCapturedContext: true);
+
+await pipeline.ExecuteAsync(
+ async context =>
+ {
+ // Execute your code, honoring the ContinueOnCapturedContext setting
+ await MyMethodAsync(context.CancellationToken).ConfigureAwait(context.ContinueOnCapturedContext);
+ },
+ context);
+
+// Optionally, return the ResilienceContext instance back to the pool
+// to minimize allocations and enhance performance
+ResilienceContextPool.Shared.Return(context);
+```
+
+
+### Cancellation support
+
+Asynchronous pipeline execution in Polly supports cancellation. This is facilitated through the `ExecuteAsync(...)` method overloads that accept a `CancellationToken`, or by initializing the `ResilienceContext` class with the `CancellationToken` property.
+
+The `CancellationToken` you pass to the `ExecuteAsync(...)` method serves multiple functions:
+
+- It cancels resilience actions such as retries, wait times between retries, or rate-limiter leases.
+- It is passed to any delegate executed by the strategy as a `CancellationToken` parameter, enabling cancellation during the delegate's execution.
+- Is consistent with the .NET Base Class Library's (BCL) behavior in `Task.Run(...)`, if the cancellation token is cancelled before execution begins, the user-defined delegate will not execute at all.
+
+
+```cs
+// Execute your code with cancellation support
+await pipeline.ExecuteAsync(
+ async token => await MyMethodAsync(token),
+ cancellationToken);
+
+// Use ResilienceContext for more advanced scenarios
+ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken: cancellationToken);
+
+await pipeline.ExecuteAsync(
+ async context => await MyMethodAsync(context.CancellationToken),
+ context);
+```
+
+
+## Thread safety
+
+All Polly resilience strategies are fully thread-safe. You can safely re-use strategies at multiple call sites, and execute through strategies concurrently on different threads.
+
+> [!IMPORTANT]
+> While the internal operation of the strategy is thread-safe, this does not automatically make delegates you execute through the strategy thread-safe: if delegates you execute through the strategy are not thread-safe, they remain not thread-safe.
diff --git a/docs/resilience-context.md b/docs/resilience-context.md
new file mode 100644
index 0000000000..b6b62b2c6f
--- /dev/null
+++ b/docs/resilience-context.md
@@ -0,0 +1,105 @@
+# Resilience Context
+
+> [!NOTE]
+> This is documentation for the upcoming Polly v8 release.
+
+The `ResilienceContext` class in Polly provides an execution-scoped instance that accompanies each execution through a Polly resilience strategy. This class serves to share context and facilitate information exchange between the pre-execution, mid-execution, and post-execution phases.
+
+The resilience context exposes several properties:
+
+- `OperationKey`: A user-defined identifier for the operation.
+- `CancellationToken`: The cancellation token linked to the operation.
+- `Properties`: An instance of `ResilienceProperties` for attaching custom data to the context.
+- `ContinueOnCapturedContext`: Specifies whether the asynchronous execution should continue on the captured context.
+
+## Usage
+
+Below is an example demonstrating how to work with `ResilienceContext`:
+
+
+```cs
+// Retrieve a context with a cancellation token
+ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken);
+
+// Attach custom data to the context
+
+context.Properties.Set(MyResilienceKeys.Key1, "my-data");
+context.Properties.Set(MyResilienceKeys.Key2, 123);
+
+// Utilize the context in a resilience pipeline
+ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
+ .AddRetry(new()
+ {
+ OnRetry = static args =>
+ {
+ // Retrieve custom data from the context, if available
+ if (args.Context.Properties.TryGetValue(MyResilienceKeys.Key1, out var data))
+ {
+ Console.WriteLine("OnRetry, Custom Data: {0}", data);
+ }
+
+ return default;
+ }
+ })
+ .Build();
+
+// Execute the resilience pipeline asynchronously
+await pipeline.ExecuteAsync(
+ async context =>
+ {
+ // Insert your execution logic here
+ },
+ context);
+
+// Return the context to the pool
+ResilienceContextPool.Shared.Return(context);
+```
+
+
+Where `ResilienceKeys` is defined as:
+
+
+```cs
+public static class MyResilienceKeys
+{
+ public static readonly ResiliencePropertyKey Key1 = new("my-key-1");
+
+ public static readonly ResiliencePropertyKey Key2 = new("my-key-2");
+}
+```
+
+
+## Resilient context pooling
+
+
+The `ResilienceContext` object is resource-intensive to create, and recreating it for each execution would negatively impact performance. To address this issue, Polly provides a `ResilienceContextPool`. This pool allows you to obtain and reuse `ResilienceContext` instances. Once you've finished using a context instance, you can return it to the pool. This action will reset the context to its initial state, making it available for reuse.
+
+
+The `ResilienceContextPool` offers several `Get` methods. These methods not only allow you to retrieve a `ResilienceContext` instance, but also enable you to initialize some of its properties at the time of retrieval.
+
+
+```cs
+// Retrieve a context with a cancellation token
+ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken);
+
+try
+{
+ // Retrieve a context with a specific operation key
+ context = ResilienceContextPool.Shared.Get("my-operation-key", cancellationToken);
+
+ // Retrieve a context with multiple properties
+ context = ResilienceContextPool.Shared.Get(
+ operationKey: "my-operation-key",
+ continueOnCapturedContext: true,
+ cancellationToken: cancellationToken);
+
+ // Use the pool here
+}
+finally
+{
+ // Returning the context back to the pool is recommended, but not required as it reduces the allocations.
+ // It is also OK to not return the context in case of exceptions, if you want to avoid try-catch blocks.
+ ResilienceContextPool.Shared.Return(context);
+}
+```
+
diff --git a/docs/resilience-pipeline-registry.md b/docs/resilience-pipeline-registry.md
new file mode 100644
index 0000000000..06e476142f
--- /dev/null
+++ b/docs/resilience-pipeline-registry.md
@@ -0,0 +1,18 @@
+# Resilience Pipeline Registry
+
+> [!NOTE]
+> This is documentation for the upcoming Polly v8 release.
+
+The `ResiliencePipelineRegistry` is a generic class that provides the following functionalities:
+
+- Thread-safe retrieval and dynamic creation of both generic and non-generic resilience pipelines.
+- Dynamic reloading of resilience pipelines when configurations change.
+- Support for registering both generic and non-generic resilience pipeline builders, enabling dynamic pipeline instance creation.
+- Automatic resource management, including disposal of resources tied to resilience pipelines.
+
+> [!NOTE]
+> The generic `TKey` parameter specifies the key type used for caching individual resilience pipelines within the registry. In most use-cases, you will be working with `ResiliencePipelineRegistry`.
+
+## Usage
+
+🚧 This documentation is being written as part of the Polly v8 release.
diff --git a/docs/resilience-pipelines.md b/docs/resilience-pipelines.md
new file mode 100644
index 0000000000..d1d075b819
--- /dev/null
+++ b/docs/resilience-pipelines.md
@@ -0,0 +1,139 @@
+# Resilience pipelines
+
+> [!NOTE]
+> This is documentation for the upcoming Polly v8 release.
+
+The `ResiliencePipeline` allows executing arbitrary user-provided callbacks. It is a combination of one or more resilience strategies.
+
+## Usage
+
+The `ResiliencePipeline` allow executing various synchronous and asynchronous user-provided callbacks as seen in the examples below:
+
+
+```cs
+// Creating a new resilience pipeline
+ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
+ .AddConcurrencyLimiter(100)
+ .Build();
+
+// Executing an asynchronous void callback
+await pipeline.ExecuteAsync(
+ async token => await MyMethodAsync(token),
+ cancellationToken);
+
+// Executing a synchronous void callback
+pipeline.Execute(() => MyMethod());
+
+// Executing an asynchronous callback that returns a value
+await pipeline.ExecuteAsync(
+ async token => await httpClient.GetAsync(endpoint, token),
+ cancellationToken);
+
+// Executing an asynchronous callback without allocating a lambda
+await pipeline.ExecuteAsync(
+ static async (state, token) => await state.httpClient.GetAsync(state.endpoint, token),
+ (httpClient, endpoint), // State provided here
+ cancellationToken);
+
+// Executing an asynchronous callback and passing custom data
+
+// 1. Retrieve a context from the shared pool
+ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken);
+
+// 2. Add custom data to the context
+context.Properties.Set(new ResiliencePropertyKey("my-custom-data"), "my-custom-data");
+
+// 3. Execute the callback
+await pipeline.ExecuteAsync(static async context =>
+{
+ // Retrieve custom data from the context
+ var customData = context.Properties.GetValue(
+ new ResiliencePropertyKey("my-custom-data"),
+ "default-value");
+
+ Console.WriteLine("Custom Data: {0}", customData);
+
+ await MyMethodAsync(context.CancellationToken);
+},
+context);
+
+// 4. Optionally, return the context to the shared pool
+ResilienceContextPool.Shared.Return(context);
+```
+
+
+The above samples demonstrate how to use the resilience pipeline within the same scope. Additionally, consider the following:
+
+- Separate the resilience pipeline's definition from its usage. Inject pipelines into the code that will consume them. This [facilitates various unit-testing scenarios](https://github.com/App-vNext/Polly/wiki/Unit-testing-with-Polly---with-examples).
+- If your application uses Polly in multiple locations, define all pipelines at startup using [`ResiliencePipelineRegistry`](/docs/resilience-pipeline-registry.md) or using the `AddResiliencePipeline` extension. This is a common approach in .NET Core applications. For example, you could create your own extension method on `IServiceCollection` to configure pipelines consumed elsewhere in your application.
+
+
+```cs
+public static void ConfigureMyPipelines(IServiceCollection services)
+{
+ services.AddResiliencePipeline("pipeline-A", builder => builder.AddConcurrencyLimiter(100));
+ services.AddResiliencePipeline("pipeline-B", builder => builder.AddRetry(new()));
+
+ // Later, resolve the pipeline by name using ResiliencePipelineProvider or ResiliencePipelineRegistry
+ var pipelineProvider = services.BuildServiceProvider().GetRequiredService>();
+ pipelineProvider.GetPipeline("pipeline-A").Execute(() => { });
+}
+```
+
+
+## Empty resilience pipeline
+
+The empty resilience pipeline is a special construct that lacks any resilience strategies. You can access it through the following ways:
+
+- `ResiliencePipeline.Empty`
+- `ResiliencePipeline.Empty`
+
+This is particularly useful in test scenarios where implementing resilience strategies could slow down the test execution or over-complicate test setup.
+
+## Retrieving execution results with `Outcome`
+
+The `ResiliencePipeline` class provides the `ExecuteOutcomeAsync(...)` method, which is designed to never throw exceptions. Instead, it stores either the result or the exception within an `Outcome` struct.
+
+
+```cs
+// Acquire a ResilienceContext from the pool
+ResilienceContext context = ResilienceContextPool.Shared.Get();
+
+// Execute the pipeline and store the result in an Outcome
+Outcome outcome = await pipeline.ExecuteOutcomeAsync(
+ static async (context, state) =>
+ {
+ Console.WriteLine("State: {0}", state);
+
+ try
+ {
+ await MyMethodAsync(context.CancellationToken);
+
+ // Use static utility methods from Outcome to easily create an Outcome instance
+ return Outcome.FromResult(true);
+ }
+ catch (Exception e)
+ {
+ // Create an Outcome instance that holds the exception
+ return Outcome.FromException(e);
+ }
+ },
+ context,
+ "my-state");
+
+// Return the acquired ResilienceContext to the pool
+ResilienceContextPool.Shared.Return(context);
+
+// Evaluate the outcome
+if (outcome.Exception is not null)
+{
+ Console.WriteLine("Execution Failed: {0}", outcome.Exception.Message);
+}
+else
+{
+ Console.WriteLine("Execution Result: {0}", outcome.Result);
+}
+```
+
+
+Use `ExecuteOutcomeAsync(...)` in high-performance scenarios where you wish to avoid re-throwing exceptions. Keep in mind that Polly's resilience strategies also make use of the `Outcome` struct to prevent unnecessary exception throwing.
diff --git a/docs/resilience-strategies.md b/docs/resilience-strategies.md
new file mode 100644
index 0000000000..40a246c0c6
--- /dev/null
+++ b/docs/resilience-strategies.md
@@ -0,0 +1,68 @@
+# Resilience Strategies
+
+> [!NOTE]
+> This is documentation for the upcoming Polly v8 release.
+
+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.
+
+## Usage
+
+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` or just one of them. Proactive strategies like timeout or rate limiter are available for both types of builders, while specialized reactive strategies are only available for `ResiliencePipelineBuilder`. Adding multiple resilience strategies is supported.
+
+Each resilience strategy provides:
+
+- Extensions for the resilience strategy builders.
+- Configuration options (e.g., `RetryStrategyOptions`) to specify the strategy's behavior.
+
+Here's an simple example:
+
+
+```cs
+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.
+
+## Fault-handling in reactive strategies
+
+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.
+
+This is demonstrated below:
+
+
+```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();
+
+// PredicateBuilder can simplify the setup of the ShouldHandle predicate.
+options.ShouldHandle = new PredicateBuilder()
+ .HandleResult(response => !response.IsSuccessStatusCode)
+ .Handle();
+
+// For greater flexibility, you can directly use the ShouldHandle delegate with switch expressions.
+options.ShouldHandle = args => args.Outcome switch
+{
+ // 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()
+};
+```
+
+
+Some additional notes from the preceding example:
+
+- `PredicateBuilder` is a utility API designed to make configuring predicates easier.
+- `PredicateResult.True()` is shorthand for `new ValueTask(true)`.
+- All `ShouldHandle` predicates are asynchronous and have the type `Func, ValueTask>`. The `Args` serves as a placeholder, and each strategy defines its own arguments.
diff --git a/docs/simmy.md b/docs/simmy.md
index e95a71d98e..1d3ed0a825 100644
--- a/docs/simmy.md
+++ b/docs/simmy.md
@@ -3,4 +3,4 @@
[Simmy][simmy] is a major new companion project adding a chaos-engineering and fault-injection dimension to Polly, through the provision of policies to selectively inject faults or latency.
Head over to the [Simmy][simmy] repo to find out more.
-[simmy]: https://github.com/Polly-Contrib/Simmy
\ No newline at end of file
+[simmy]: https://github.com/Polly-Contrib/Simmy
diff --git a/docs/telemetry.md b/docs/telemetry.md
new file mode 100644
index 0000000000..814a30bd3a
--- /dev/null
+++ b/docs/telemetry.md
@@ -0,0 +1,3 @@
+# Telemetry
+
+🚧 This documentation is being written as part of the Polly v8 release.
diff --git a/src/Polly.Core/README.md b/src/Polly.Core/README.md
index 7ceeaced0a..7fad113059 100644
--- a/src/Polly.Core/README.md
+++ b/src/Polly.Core/README.md
@@ -17,23 +17,23 @@ public abstract class ResiliencePipeline
public void Execute(Action callback);
public TResult Execute(Func callback);
-
+
public Task ExecuteAsync(
- Func callback,
+ Func callback,
CancellationToken cancellationToken = default);
-
+
public Task ExecuteAsync(
- Func> callback,
+ Func> callback,
CancellationToken cancellationToken = default);
-
+
public ValueTask ExecuteAsync(
- Func callback,
+ Func callback,
CancellationToken cancellationToken = default);
-
+
public ValueTask ExecuteAsync(
- Func> callback,
+ Func> callback,
CancellationToken cancellationToken = default);
-
+
// Other methods are omitted for simplicity
}
```
@@ -60,13 +60,16 @@ The `ResiliencePipeline` class unifies the four different policies that were ava
> [!NOTE]
> Polly also provides a `ResiliencePipeline` class. This specialized pipeline is useful for scenarios where the consumer is concerned with only a single type of result.
-## Resilience Strategies
+### Building resilience pipeline
-The resilience pipeline may consist of one or more individual resilience strategies. Polly V8 categorizes resilience strategies into the following building blocks:
+- Use `ResiliencePipelineBuilder` to construct an instance of `ResiliencePipeline`.
+- Use `ResiliencePipelineBuilder` to construct an instance of `ResiliencePipeline`.
- `ResilienceStrategy`: Base class for all proactive resilience strategies.
- `ResilienceStrategy`: Base class for all reactive resilience strategies.
+Polly provides a variety of extension methods to add resilience strategies to each type of builder.
+
### Example: Custom Proactive Strategy
Here's an example of a proactive strategy that executes a user-provided callback:
@@ -106,6 +109,8 @@ The API exposes the following builder classes for creating resilience pipelines:
To construct a resilience pipeline, chain various extensions on the `ResiliencePipelineBuilder` and conclude with a `Build` method call.
+Explore [resilience pipelines](../../docs/resilience-pipelines.md) page to explore the consumption of resilience pipelines from the user perspective.
+
### Creating a non-generic pipeline
@@ -184,7 +189,6 @@ Recommended signatures for these delegates are:
- `Func, ValueTask>` (Reactive)
- `Func>` (Proactive)
-### Delegate Arguments
These delegates accept either `Args` or `Args` arguments, which encapsulate event information. Note that all these delegates are asynchronous and return a `ValueTask`.
diff --git a/src/Snippets/Docs/CircuitBreaker.cs b/src/Snippets/Docs/CircuitBreaker.cs
new file mode 100644
index 0000000000..2fb66df26e
--- /dev/null
+++ b/src/Snippets/Docs/CircuitBreaker.cs
@@ -0,0 +1,69 @@
+using System.Net;
+using System.Net.Http;
+using Polly;
+using Polly.CircuitBreaker;
+using Snippets.Docs.Utils;
+
+namespace Snippets.Docs;
+
+internal static class CircuitBreaker
+{
+ public static async Task Usage()
+ {
+ #region circuit-breaker
+
+ // 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();
+
+ #endregion
+ }
+}
diff --git a/src/Snippets/Docs/Fallback.cs b/src/Snippets/Docs/Fallback.cs
new file mode 100644
index 0000000000..3fc375f38b
--- /dev/null
+++ b/src/Snippets/Docs/Fallback.cs
@@ -0,0 +1,65 @@
+using Polly;
+using Polly.Fallback;
+using Snippets.Docs.Utils;
+
+namespace Snippets.Docs;
+
+internal static class Fallback
+{
+ public static void Usage()
+ {
+ #region fallback
+
+ // 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
+ }
+ });
+
+ #endregion
+ }
+
+ public class UserAvatar
+ {
+ public static readonly UserAvatar Blank = new();
+
+ public static UserAvatar GetRandomAvatar() => new();
+ }
+}
diff --git a/src/Snippets/Docs/General.cs b/src/Snippets/Docs/General.cs
new file mode 100644
index 0000000000..3880cc6ba1
--- /dev/null
+++ b/src/Snippets/Docs/General.cs
@@ -0,0 +1,57 @@
+using Polly;
+
+namespace Snippets.Docs;
+
+internal static class General
+{
+ public static async Task SynchronizationContext()
+ {
+ ResiliencePipeline pipeline = ResiliencePipeline.Empty;
+
+ #region synchronization-context
+
+ // Retrieve an instance of ResilienceContext from the pool
+ // with the ContinueOnCapturedContext property set to true
+ ResilienceContext context = ResilienceContextPool.Shared.Get(continueOnCapturedContext: true);
+
+ await pipeline.ExecuteAsync(
+ async context =>
+ {
+ // Execute your code, honoring the ContinueOnCapturedContext setting
+ await MyMethodAsync(context.CancellationToken).ConfigureAwait(context.ContinueOnCapturedContext);
+ },
+ context);
+
+ // Optionally, return the ResilienceContext instance back to the pool
+ // to minimize allocations and enhance performance
+ ResilienceContextPool.Shared.Return(context);
+
+ #endregion
+
+ static async Task MyMethodAsync(CancellationToken cancellationToken) => await Task.Delay(100, cancellationToken);
+ }
+
+ public static async Task CancellationTokenSample()
+ {
+ ResiliencePipeline pipeline = ResiliencePipeline.Empty;
+ var cancellationToken = CancellationToken.None;
+
+ #region cancellation-token
+
+ // Execute your code with cancellation support
+ await pipeline.ExecuteAsync(
+ async token => await MyMethodAsync(token),
+ cancellationToken);
+
+ // Use ResilienceContext for more advanced scenarios
+ ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken: cancellationToken);
+
+ await pipeline.ExecuteAsync(
+ async context => await MyMethodAsync(context.CancellationToken),
+ context);
+
+ #endregion
+
+ static async Task MyMethodAsync(CancellationToken cancellationToken) => await Task.Delay(100, cancellationToken);
+ }
+}
diff --git a/src/Snippets/Docs/Hedging.cs b/src/Snippets/Docs/Hedging.cs
new file mode 100644
index 0000000000..09e2723c1d
--- /dev/null
+++ b/src/Snippets/Docs/Hedging.cs
@@ -0,0 +1,52 @@
+using System.Net;
+using System.Net.Http;
+using Polly;
+using Polly.Hedging;
+using Snippets.Docs.Utils;
+
+namespace Snippets.Docs;
+
+internal static class Hedging
+{
+ public static void Usage()
+ {
+ #region hedging
+
+ // 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;
+ }
+ });
+
+ #endregion
+ }
+}
diff --git a/src/Snippets/Docs/RateLimiter.cs b/src/Snippets/Docs/RateLimiter.cs
new file mode 100644
index 0000000000..c46886007b
--- /dev/null
+++ b/src/Snippets/Docs/RateLimiter.cs
@@ -0,0 +1,88 @@
+using System.Threading.RateLimiting;
+using Polly;
+using Polly.RateLimiting;
+
+namespace Snippets.Docs;
+
+internal static class RateLimiter
+{
+ public static void Usage()
+ {
+ #region rate-limiter
+
+ // 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);
+ }
+ });
+
+ #endregion
+ }
+
+ public static async Task Execution()
+ {
+ var cancellationToken = CancellationToken.None;
+ var query = "dummy";
+
+ #region rate-limiter-execution
+
+ 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}");
+ }
+ }
+
+ #endregion
+ }
+
+ private static ValueTask TextSearchAsync(string query, CancellationToken token) => new("dummy");
+
+ private static string GetPartitionKey(Polly.ResilienceContext context) => string.Empty;
+}
diff --git a/src/Snippets/Docs/Readme.cs b/src/Snippets/Docs/Readme.cs
new file mode 100644
index 0000000000..b0b2d1805b
--- /dev/null
+++ b/src/Snippets/Docs/Readme.cs
@@ -0,0 +1,66 @@
+using Microsoft.Extensions.DependencyInjection;
+using Polly;
+using Polly.Registry;
+using Polly.Retry;
+
+namespace Snippets.Docs;
+
+internal static class Readme
+{
+ public static async Task QuickStart()
+ {
+ #region quick-start
+
+ // 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
+ });
+
+ #endregion
+ }
+
+ public static async Task QuickStartDi()
+ {
+ #region quick-start-di
+
+ 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
+ });
+
+ #endregion
+ }
+}
diff --git a/src/Snippets/Docs/ResilienceContextUsage.cs b/src/Snippets/Docs/ResilienceContextUsage.cs
new file mode 100644
index 0000000000..f89130a088
--- /dev/null
+++ b/src/Snippets/Docs/ResilienceContextUsage.cs
@@ -0,0 +1,94 @@
+using Polly;
+
+namespace Snippets.Docs;
+
+internal static class ResilienceContextUsage
+{
+ #region resilience-keys
+
+ public static class MyResilienceKeys
+ {
+ public static readonly ResiliencePropertyKey Key1 = new("my-key-1");
+
+ public static readonly ResiliencePropertyKey Key2 = new("my-key-2");
+ }
+
+ #endregion
+
+ public static async Task Usage()
+ {
+ var cancellationToken = CancellationToken.None;
+
+ #region resilience-context
+
+ // Retrieve a context with a cancellation token
+ ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken);
+
+ // Attach custom data to the context
+
+ context.Properties.Set(MyResilienceKeys.Key1, "my-data");
+ context.Properties.Set(MyResilienceKeys.Key2, 123);
+
+ // Utilize the context in a resilience pipeline
+ ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
+ .AddRetry(new()
+ {
+ OnRetry = static args =>
+ {
+ // Retrieve custom data from the context, if available
+ if (args.Context.Properties.TryGetValue(MyResilienceKeys.Key1, out var data))
+ {
+ Console.WriteLine("OnRetry, Custom Data: {0}", data);
+ }
+
+ return default;
+ }
+ })
+ .Build();
+
+ // Execute the resilience pipeline asynchronously
+ await pipeline.ExecuteAsync(
+ async context =>
+ {
+ // Insert your execution logic here
+ },
+ context);
+
+ // Return the context to the pool
+ ResilienceContextPool.Shared.Return(context);
+
+ #endregion
+ }
+
+ public static async Task Pooling()
+ {
+ var cancellationToken = CancellationToken.None;
+
+ #region resilience-context-pool
+
+ // Retrieve a context with a cancellation token
+ ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken);
+
+ try
+ {
+ // Retrieve a context with a specific operation key
+ context = ResilienceContextPool.Shared.Get("my-operation-key", cancellationToken);
+
+ // Retrieve a context with multiple properties
+ context = ResilienceContextPool.Shared.Get(
+ operationKey: "my-operation-key",
+ continueOnCapturedContext: true,
+ cancellationToken: cancellationToken);
+
+ // Use the pool here
+ }
+ finally
+ {
+ // Returning the context back to the pool is recommended, but not required as it reduces the allocations.
+ // It is also OK to not return the context in case of exceptions, if you want to avoid try-catch blocks.
+ ResilienceContextPool.Shared.Return(context);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Snippets/Docs/ResiliencePipelines.cs b/src/Snippets/Docs/ResiliencePipelines.cs
new file mode 100644
index 0000000000..b281ca5a00
--- /dev/null
+++ b/src/Snippets/Docs/ResiliencePipelines.cs
@@ -0,0 +1,140 @@
+using System.Net.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Polly;
+using Polly.Registry;
+
+namespace Snippets.Docs;
+
+#pragma warning disable CA1031 // Do not catch general exception types
+
+internal static class ResiliencePipelines
+{
+ public static async Task Usage()
+ {
+ var cancellationToken = CancellationToken.None;
+ var httpClient = new HttpClient();
+ var endpoint = new Uri("https://endpoint");
+
+ #region resilience-pipeline-usage
+
+ // Creating a new resilience pipeline
+ ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
+ .AddConcurrencyLimiter(100)
+ .Build();
+
+ // Executing an asynchronous void callback
+ await pipeline.ExecuteAsync(
+ async token => await MyMethodAsync(token),
+ cancellationToken);
+
+ // Executing a synchronous void callback
+ pipeline.Execute(() => MyMethod());
+
+ // Executing an asynchronous callback that returns a value
+ await pipeline.ExecuteAsync(
+ async token => await httpClient.GetAsync(endpoint, token),
+ cancellationToken);
+
+ // Executing an asynchronous callback without allocating a lambda
+ await pipeline.ExecuteAsync(
+ static async (state, token) => await state.httpClient.GetAsync(state.endpoint, token),
+ (httpClient, endpoint), // State provided here
+ cancellationToken);
+
+ // Executing an asynchronous callback and passing custom data
+
+ // 1. Retrieve a context from the shared pool
+ ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken);
+
+ // 2. Add custom data to the context
+ context.Properties.Set(new ResiliencePropertyKey("my-custom-data"), "my-custom-data");
+
+ // 3. Execute the callback
+ await pipeline.ExecuteAsync(static async context =>
+ {
+ // Retrieve custom data from the context
+ var customData = context.Properties.GetValue(
+ new ResiliencePropertyKey("my-custom-data"),
+ "default-value");
+
+ Console.WriteLine("Custom Data: {0}", customData);
+
+ await MyMethodAsync(context.CancellationToken);
+ },
+ context);
+
+ // 4. Optionally, return the context to the shared pool
+ ResilienceContextPool.Shared.Return(context);
+
+ #endregion
+ }
+
+ #region resilience-pipeline-di-usage
+
+ public static void ConfigureMyPipelines(IServiceCollection services)
+ {
+ services.AddResiliencePipeline("pipeline-A", builder => builder.AddConcurrencyLimiter(100));
+ services.AddResiliencePipeline("pipeline-B", builder => builder.AddRetry(new()));
+
+ // Later, resolve the pipeline by name using ResiliencePipelineProvider or ResiliencePipelineRegistry
+ var pipelineProvider = services.BuildServiceProvider().GetRequiredService>();
+ pipelineProvider.GetPipeline("pipeline-A").Execute(() => { });
+ }
+
+ #endregion
+
+ public static async Task ExecuteOutcomeAsync()
+ {
+ var pipeline = Polly.ResiliencePipeline.Empty;
+
+ #region resilience-pipeline-outcome
+
+ // Acquire a ResilienceContext from the pool
+ ResilienceContext context = ResilienceContextPool.Shared.Get();
+
+ // Execute the pipeline and store the result in an Outcome
+ Outcome outcome = await pipeline.ExecuteOutcomeAsync(
+ static async (context, state) =>
+ {
+ Console.WriteLine("State: {0}", state);
+
+ try
+ {
+ await MyMethodAsync(context.CancellationToken);
+
+ // Use static utility methods from Outcome to easily create an Outcome instance
+ return Outcome.FromResult(true);
+ }
+ catch (Exception e)
+ {
+ // Create an Outcome instance that holds the exception
+ return Outcome.FromException(e);
+ }
+ },
+ context,
+ "my-state");
+
+ // Return the acquired ResilienceContext to the pool
+ ResilienceContextPool.Shared.Return(context);
+
+ // Evaluate the outcome
+ if (outcome.Exception is not null)
+ {
+ Console.WriteLine("Execution Failed: {0}", outcome.Exception.Message);
+ }
+ else
+ {
+ Console.WriteLine("Execution Result: {0}", outcome.Result);
+ }
+
+ #endregion
+ }
+
+ private static Task MyMethodAsync(CancellationToken cancellationToken) => Task.Delay(1000, cancellationToken);
+
+ private static void MyMethod()
+ {
+ // Do nothing
+ }
+}
+
diff --git a/src/Snippets/Docs/ResilienceStrategies.cs b/src/Snippets/Docs/ResilienceStrategies.cs
new file mode 100644
index 0000000000..02fd87080d
--- /dev/null
+++ b/src/Snippets/Docs/ResilienceStrategies.cs
@@ -0,0 +1,51 @@
+using System.Net.Http;
+using Polly;
+using Polly.Retry;
+using Polly.Timeout;
+
+namespace Snippets.Docs;
+
+internal static class ResilienceStrategies
+{
+ public static async Task Usage()
+ {
+ #region resilience-strategy-sample
+
+ ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
+ .AddTimeout(new TimeoutStrategyOptions
+ {
+ Timeout = TimeSpan.FromSeconds(5)
+ })
+ .Build();
+
+ #endregion
+ }
+
+ public static void ShouldHandle()
+ {
+ #region should-handle
+
+ // 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();
+
+ // PredicateBuilder can simplify the setup of the ShouldHandle predicate.
+ options.ShouldHandle = new PredicateBuilder()
+ .HandleResult(response => !response.IsSuccessStatusCode)
+ .Handle();
+
+ // For greater flexibility, you can directly use the ShouldHandle delegate with switch expressions.
+ options.ShouldHandle = args => args.Outcome switch
+ {
+ // 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()
+ };
+
+ #endregion
+ }
+}
diff --git a/src/Snippets/Docs/Retry.cs b/src/Snippets/Docs/Retry.cs
new file mode 100644
index 0000000000..9d1fca8ac0
--- /dev/null
+++ b/src/Snippets/Docs/Retry.cs
@@ -0,0 +1,105 @@
+using System.Globalization;
+using System.Net.Http;
+using Polly;
+using Polly.Retry;
+using Snippets.Docs.Utils;
+
+namespace Snippets.Docs;
+
+internal static class Retry
+{
+ public static void Usage()
+ {
+ #region retry
+
+ // 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,
+ });
+
+ #endregion
+ }
+
+ private static bool TryGetDelay(HttpResponseMessage response, out TimeSpan delay)
+ {
+ if (response.Headers.TryGetValues("Retry-After", out var values) &&
+ values.FirstOrDefault() is string retryAfterValue &&
+ int.TryParse(retryAfterValue, CultureInfo.InvariantCulture, out int retryAfterSeconds))
+ {
+ delay = TimeSpan.FromSeconds(retryAfterSeconds);
+ return true;
+ }
+
+ delay = TimeSpan.Zero;
+ return false;
+ }
+}
diff --git a/src/Snippets/Docs/Timeout.cs b/src/Snippets/Docs/Timeout.cs
new file mode 100644
index 0000000000..90b7270d83
--- /dev/null
+++ b/src/Snippets/Docs/Timeout.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Net.Http;
+using Polly;
+using Polly.Timeout;
+
+namespace Snippets.Docs;
+
+internal static class Timeout
+{
+ public static async Task Usage()
+ {
+ #region timeout
+
+ // 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;
+ }
+ });
+
+ #endregion
+
+ var cancellationToken = CancellationToken.None;
+ var httpClient = new HttpClient();
+ var endpoint = new Uri("https://dummy");
+
+ #region timeout-execution
+
+ 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);
+
+ #endregion
+ }
+}
diff --git a/src/Snippets/Docs/Utils/SomeExceptionType.cs b/src/Snippets/Docs/Utils/SomeExceptionType.cs
new file mode 100644
index 0000000000..d3a57b7911
--- /dev/null
+++ b/src/Snippets/Docs/Utils/SomeExceptionType.cs
@@ -0,0 +1,18 @@
+namespace Snippets.Docs.Utils;
+
+internal class SomeExceptionType : Exception
+{
+ public SomeExceptionType(string message)
+ : base(message)
+ {
+ }
+
+ public SomeExceptionType(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ public SomeExceptionType()
+ {
+ }
+}
diff --git a/src/Snippets/Snippets.csproj b/src/Snippets/Snippets.csproj
index cedda64e52..3ad377471e 100644
--- a/src/Snippets/Snippets.csproj
+++ b/src/Snippets/Snippets.csproj
@@ -8,7 +8,8 @@
Library
false
false
- $(NoWarn);SA1123;SA1515;CA2000;CA2007;CA1303;IDE0021
+ $(NoWarn);SA1123;SA1515;CA2000;CA2007;CA1303;IDE0021;IDE0017;IDE0060;CS1998;CA1064
+ Snippets