From ca335ffedad628be2e8ed2c77a03e95dec08e9cb Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Tue, 5 Sep 2023 17:28:35 +0200 Subject: [PATCH 1/9] Prepare v8 README and docs --- README.md | 16 +- README_V8.md | 549 +++++++++++++++++++ docs/README.md | 16 +- docs/dependency-injection.md | 3 + docs/extensiblity.md | 3 + docs/general.md | 68 +++ docs/resilience-context.md | 85 +++ docs/resilience-pipeline-registry.md | 15 + docs/resilience-pipelines.md | 140 +++++ docs/resilience-strategies.md | 65 +++ docs/simmy.md | 2 +- docs/telemetry.md | 3 + src/Polly.Core/README.md | 30 +- src/Snippets/Docs/CircuitBreaker.cs | 83 +++ src/Snippets/Docs/Fallback.cs | 68 +++ src/Snippets/Docs/General.cs | 57 ++ src/Snippets/Docs/Hedging.cs | 54 ++ src/Snippets/Docs/RateLimiter.cs | 88 +++ src/Snippets/Docs/Readme.cs | 69 +++ src/Snippets/Docs/ResilienceContextUsage.cs | 79 +++ src/Snippets/Docs/ResiliencePipelines.cs | 140 +++++ src/Snippets/Docs/ResilienceStrategies.cs | 51 ++ src/Snippets/Docs/Retry.cs | 111 ++++ src/Snippets/Docs/Timeout.cs | 71 +++ src/Snippets/Docs/Utils/SomeExceptionType.cs | 18 + src/Snippets/Snippets.csproj | 3 +- 26 files changed, 1857 insertions(+), 30 deletions(-) create mode 100644 README_V8.md create mode 100644 docs/dependency-injection.md create mode 100644 docs/extensiblity.md create mode 100644 docs/general.md create mode 100644 docs/resilience-context.md create mode 100644 docs/resilience-pipeline-registry.md create mode 100644 docs/resilience-pipelines.md create mode 100644 docs/resilience-strategies.md create mode 100644 docs/telemetry.md create mode 100644 src/Snippets/Docs/CircuitBreaker.cs create mode 100644 src/Snippets/Docs/Fallback.cs create mode 100644 src/Snippets/Docs/General.cs create mode 100644 src/Snippets/Docs/Hedging.cs create mode 100644 src/Snippets/Docs/RateLimiter.cs create mode 100644 src/Snippets/Docs/Readme.cs create mode 100644 src/Snippets/Docs/ResilienceContextUsage.cs create mode 100644 src/Snippets/Docs/ResiliencePipelines.cs create mode 100644 src/Snippets/Docs/ResilienceStrategies.cs create mode 100644 src/Snippets/Docs/Retry.cs create mode 100644 src/Snippets/Docs/Timeout.cs create mode 100644 src/Snippets/Docs/Utils/SomeExceptionType.cs diff --git a/README.md b/README.md index dbc9fd54cb1..03d6395351a 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 00000000000..71c6a1c963a --- /dev/null +++ b/README_V8.md @@ -0,0 +1,549 @@ +# 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: +// - 3 retry attempts +// - 1-second delay between retries +// - Handles all exceptions except OperationCanceledException +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/en-us/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 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 by the string-based name +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 take pro-active actions 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 +// To use the default retry options with pre-configured settings: +// +// MaxRetryAttempts: 3, +// Delay: 2 seconds, +// Backoff Type: Constant, +// UseJitter: false, +// ShouldHandle: Handles all exceptions except OperationCanceledException +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 depth visit [retry strategy documentation](https://github.com/App-vNext/Polly/wiki/Retry). + +### Circuit Breaker + + +```cs +// Use the default Circuit Breaker options with pre-configured settings: +// +// FailureRatio: 0.1 — Opens the circuit if 10% of the calls fail +// BreakDuration: 30 seconds — Duration the circuit stays open before another try +// MinimumThroughput: 100 — Minimum number of calls required before the circuit can break +// SamplingDuration: 30 seconds — Time window for monitoring calls before allowing the circuit to break +// ShouldHandle: Handles all exceptions except for OperationCanceledException +new ResiliencePipelineBuilder().AddCircuitBreaker(new CircuitBreakerStrategyOptions()); + +// Configure custom settings for the Circuit Breaker: +// +// 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 accept actions 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](http://techblog.netflix.com/2011/12/making-netflix-api-more-resilient.html) +- [Circuit Breaker by Martin Fowler](http://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 substitute value if an operation fails. +new ResiliencePipelineBuilder() + .AddFallback(new FallbackStrategyOptions + { + ShouldHandle = new PredicateBuilder() + .Handle() + .HandleResult(r => r is null), + FallbackAction = args => + { + return 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 a default hedging strategy that retries once if the previous +// execution didn't complete within 2 seconds or failed due to an exception. +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 a 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 {0}", 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://msdn.microsoft.com/en-us/library/dd997364.aspx). You must use `Execute/Async(...)` overloads taking a `CancellationToken`, and the executed delegate must honor that `CancellationToken`. + + +```cs +// To add a default 30-second timeout +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 +// Create a rate limiter with default options that limit to 1000 concurrent executions and no queue. +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). + +## Release notes + +- The [changelog](https://github.com/App-vNext/Polly/blob/main/CHANGELOG.md) describes changes by release. +- We tag Pull Requests and Issues with [milestones](https://github.com/App-vNext/Polly/milestones) which match to NuGet package release numbers. +- Breaking changes are called out in the wiki ([v7](https://github.com/App-vNext/Polly/wiki/Polly-v7-breaking-changes); [v6](https://github.com/App-vNext/Polly/wiki/Polly-v6-breaking-changes)) with simple notes on any necessary steps to upgrade. + +## 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 a3daacc1092..103992658d7 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 (older 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 00000000000..64e9c91009a --- /dev/null +++ b/docs/dependency-injection.md @@ -0,0 +1,3 @@ +# Dependency Injection + +TODO diff --git a/docs/extensiblity.md b/docs/extensiblity.md new file mode 100644 index 00000000000..6b7b32c3ac5 --- /dev/null +++ b/docs/extensiblity.md @@ -0,0 +1,3 @@ +# Extensibility + +TODO diff --git a/docs/general.md b/docs/general.md new file mode 100644 index 00000000000..02ee077d4dc --- /dev/null +++ b/docs/general.md @@ -0,0 +1,68 @@ +# General + +## Supported targets + +Polly targets .NET Standard 2.0+ ([coverage](https://docs.microsoft.com/en-us/dotnet/standard/net-standard#net-implementation-support): .NET Core 2.0+, .NET Core 3.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. +- Consistent with the .NET Base Class Library's 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 magically 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 00000000000..bc48d4e377e --- /dev/null +++ b/docs/resilience-context.md @@ -0,0 +1,85 @@ +# Resilience Context + +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 +var key1 = new ResiliencePropertyKey("my-key-1"); +var key2 = new ResiliencePropertyKey("my-key-2"); + +context.Properties.Set(key1, "my-data"); +context.Properties.Set(key2, 123); + +// Utilize the context in a resilience pipeline +ResiliencePipeline pipeline = new ResiliencePipelineBuilder() + .AddRetry(new() + { + OnRetry = args => + { + // Retrieve custom data from the context, if available + if (args.Context.Properties.TryGetValue(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); +``` + + +## 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); + +// 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 + +// Return the context to the pool +ResilienceContextPool.Shared.Return(context); +``` + diff --git a/docs/resilience-pipeline-registry.md b/docs/resilience-pipeline-registry.md new file mode 100644 index 00000000000..15dfaf64631 --- /dev/null +++ b/docs/resilience-pipeline-registry.md @@ -0,0 +1,15 @@ +# Resilience Pipeline Registry + +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 + +TODO diff --git a/docs/resilience-pipelines.md b/docs/resilience-pipelines.md new file mode 100644 index 00000000000..768eddb6c9e --- /dev/null +++ b/docs/resilience-pipelines.md @@ -0,0 +1,140 @@ +# Resilience pipelines + +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 bellow: + + +```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/registry.md-TODO) 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(() => { }); +} +``` + + +> [!NOTE] +> In certain scenarios, such as testing, you may want to use the ResiliencePipeline.Empty instance. This instance executes the callback without applying any additional logic. + +## 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. + +## 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); +} +``` + + +Utilize `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 00000000000..e790fd0fd4e --- /dev/null +++ b/docs/resilience-strategies.md @@ -0,0 +1,65 @@ +# Resilience Strategies + +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 bellow: + + +```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 a 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 e95a71d98e2..1d3ed0a825c 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 00000000000..ecb467b8800 --- /dev/null +++ b/docs/telemetry.md @@ -0,0 +1,3 @@ +# Telemetry + +TODO diff --git a/src/Polly.Core/README.md b/src/Polly.Core/README.md index 7ceeaced0a0..9f55e357ea6 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 @@ -182,9 +187,8 @@ Recommended signatures for these delegates are: ### Generators - `Func, ValueTask>` (Reactive) -- `Func>` (Proactive) +- `Func>` (Non-Reactive) -### 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 00000000000..4863706393c --- /dev/null +++ b/src/Snippets/Docs/CircuitBreaker.cs @@ -0,0 +1,83 @@ +using System.Net; +using System.Net.Http; +using Polly; +using Polly.CircuitBreaker; +using Snippets.Docs.Utils; + +namespace Snippets.Docs; + +#pragma warning disable S103 // Lines should not be too long + +internal static class CircuitBreaker +{ + public static async Task CircuitBreakerExample() + { + #region circuit-breaker + + // Use the default Circuit Breaker options with pre-configured settings: + // + // FailureRatio: 0.1 — Opens the circuit if 10% of the calls fail + // BreakDuration: 30 seconds — Duration the circuit stays open before another try + // MinimumThroughput: 100 — Minimum number of calls required before the circuit can break + // SamplingDuration: 30 seconds — Time window for monitoring calls before allowing the circuit to break + // ShouldHandle: Handles all exceptions except for OperationCanceledException + new ResiliencePipelineBuilder().AddCircuitBreaker(new CircuitBreakerStrategyOptions()); + + // Configure custom settings for the Circuit Breaker: + // + // 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 accept actions again. + await manualControl.CloseAsync(); + + #endregion + } +} diff --git a/src/Snippets/Docs/Fallback.cs b/src/Snippets/Docs/Fallback.cs new file mode 100644 index 00000000000..78e02a0ec9d --- /dev/null +++ b/src/Snippets/Docs/Fallback.cs @@ -0,0 +1,68 @@ +using Polly; +using Polly.Fallback; +using Snippets.Docs.Utils; + +namespace Snippets.Docs; + +internal static class Fallback +{ + public static void FallbackExample() + { + #region fallback + + // Use a substitute value if an operation fails. + new ResiliencePipelineBuilder() + .AddFallback(new FallbackStrategyOptions + { + ShouldHandle = new PredicateBuilder() + .Handle() + .HandleResult(r => r is null), + FallbackAction = args => + { + return 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 00000000000..3880cc6ba1c --- /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 00000000000..94e91867c4e --- /dev/null +++ b/src/Snippets/Docs/Hedging.cs @@ -0,0 +1,54 @@ +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 HedgingSample() + { + #region hedging + + // Add a default hedging strategy that retries once if the previous + // execution didn't complete within 2 seconds or failed due to an exception. + 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 a 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 {0}", args.AttemptNumber); + return default; + } + }); + + #endregion + } +} diff --git a/src/Snippets/Docs/RateLimiter.cs b/src/Snippets/Docs/RateLimiter.cs new file mode 100644 index 00000000000..b04a74169e9 --- /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 RateLimiterUsage() + { + #region rate-limiter + + // Create a rate limiter with default options that limit to 1000 concurrent executions and no queue. + 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 RateLimiterExecution() + { + 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 00000000000..1f518bfaa67 --- /dev/null +++ b/src/Snippets/Docs/Readme.cs @@ -0,0 +1,69 @@ +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: + // - 3 retry attempts + // - 1-second delay between retries + // - Handles all exceptions except OperationCanceledException + 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 by the string-based name + 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 00000000000..0af7e0a535f --- /dev/null +++ b/src/Snippets/Docs/ResilienceContextUsage.cs @@ -0,0 +1,79 @@ +using Polly; + +namespace Snippets.Docs; + +internal static class ResilienceContextUsage +{ + public static async Task ResilienceContextSample() + { + 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 + var key1 = new ResiliencePropertyKey("my-key-1"); + var key2 = new ResiliencePropertyKey("my-key-2"); + + context.Properties.Set(key1, "my-data"); + context.Properties.Set(key2, 123); + + // Utilize the context in a resilience pipeline + ResiliencePipeline pipeline = new ResiliencePipelineBuilder() + .AddRetry(new() + { + OnRetry = args => + { + // Retrieve custom data from the context, if available + if (args.Context.Properties.TryGetValue(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 ResilienceContextPoolSample() + { + var cancellationToken = CancellationToken.None; + + #region resilience-context-pool + + // Retrieve a context with a cancellation token + ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken); + + // 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 + + // Return the context to the pool + ResilienceContextPool.Shared.Return(context); + + #endregion + } +} diff --git a/src/Snippets/Docs/ResiliencePipelines.cs b/src/Snippets/Docs/ResiliencePipelines.cs new file mode 100644 index 00000000000..cdacc4d5866 --- /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 ResiliencePipelineUsage() + { + 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 00000000000..75324663f42 --- /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 ResilienceStrategySample() + { + #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 00000000000..7b6b3e37d1b --- /dev/null +++ b/src/Snippets/Docs/Retry.cs @@ -0,0 +1,111 @@ +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 RetryExample() + { + #region retry + + // To use the default retry options with pre-configured settings: + // + // MaxRetryAttempts: 3, + // Delay: 2 seconds, + // Backoff Type: Constant, + // UseJitter: false, + // ShouldHandle: Handles all exceptions except OperationCanceledException + 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 00000000000..a2796740354 --- /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 TimeoutExample() + { + #region timeout + + // To add a default 30-second timeout + 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 00000000000..d3a57b7911e --- /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 cedda64e529..3ad377471e6 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 From c269fc9472bf6cd76f038c3eec33f583cc0a6597 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Wed, 6 Sep 2023 07:58:45 +0200 Subject: [PATCH 2/9] Cleanup the snippets --- src/Snippets/Docs/CircuitBreaker.cs | 2 +- src/Snippets/Docs/Fallback.cs | 2 +- src/Snippets/Docs/Hedging.cs | 2 +- src/Snippets/Docs/RateLimiter.cs | 4 ++-- src/Snippets/Docs/ResilienceContextUsage.cs | 4 ++-- src/Snippets/Docs/ResiliencePipelines.cs | 2 +- src/Snippets/Docs/ResilienceStrategies.cs | 2 +- src/Snippets/Docs/Retry.cs | 2 +- src/Snippets/Docs/Timeout.cs | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Snippets/Docs/CircuitBreaker.cs b/src/Snippets/Docs/CircuitBreaker.cs index 4863706393c..f0385fbd1d5 100644 --- a/src/Snippets/Docs/CircuitBreaker.cs +++ b/src/Snippets/Docs/CircuitBreaker.cs @@ -10,7 +10,7 @@ namespace Snippets.Docs; internal static class CircuitBreaker { - public static async Task CircuitBreakerExample() + public static async Task Usage() { #region circuit-breaker diff --git a/src/Snippets/Docs/Fallback.cs b/src/Snippets/Docs/Fallback.cs index 78e02a0ec9d..63953400e28 100644 --- a/src/Snippets/Docs/Fallback.cs +++ b/src/Snippets/Docs/Fallback.cs @@ -6,7 +6,7 @@ namespace Snippets.Docs; internal static class Fallback { - public static void FallbackExample() + public static void Usage() { #region fallback diff --git a/src/Snippets/Docs/Hedging.cs b/src/Snippets/Docs/Hedging.cs index 94e91867c4e..0ef98244758 100644 --- a/src/Snippets/Docs/Hedging.cs +++ b/src/Snippets/Docs/Hedging.cs @@ -8,7 +8,7 @@ namespace Snippets.Docs; internal static class Hedging { - public static void HedgingSample() + public static void Usage() { #region hedging diff --git a/src/Snippets/Docs/RateLimiter.cs b/src/Snippets/Docs/RateLimiter.cs index b04a74169e9..ea169297ab0 100644 --- a/src/Snippets/Docs/RateLimiter.cs +++ b/src/Snippets/Docs/RateLimiter.cs @@ -6,7 +6,7 @@ namespace Snippets.Docs; internal static class RateLimiter { - public static void RateLimiterUsage() + public static void Usage() { #region rate-limiter @@ -53,7 +53,7 @@ public static void RateLimiterUsage() #endregion } - public static async Task RateLimiterExecution() + public static async Task Execution() { var cancellationToken = CancellationToken.None; var query = "dummy"; diff --git a/src/Snippets/Docs/ResilienceContextUsage.cs b/src/Snippets/Docs/ResilienceContextUsage.cs index 0af7e0a535f..66c0f33d64d 100644 --- a/src/Snippets/Docs/ResilienceContextUsage.cs +++ b/src/Snippets/Docs/ResilienceContextUsage.cs @@ -4,7 +4,7 @@ namespace Snippets.Docs; internal static class ResilienceContextUsage { - public static async Task ResilienceContextSample() + public static async Task Usage() { var cancellationToken = CancellationToken.None; @@ -51,7 +51,7 @@ await pipeline.ExecuteAsync( #endregion } - public static async Task ResilienceContextPoolSample() + public static async Task Pooling() { var cancellationToken = CancellationToken.None; diff --git a/src/Snippets/Docs/ResiliencePipelines.cs b/src/Snippets/Docs/ResiliencePipelines.cs index cdacc4d5866..b281ca5a00b 100644 --- a/src/Snippets/Docs/ResiliencePipelines.cs +++ b/src/Snippets/Docs/ResiliencePipelines.cs @@ -9,7 +9,7 @@ namespace Snippets.Docs; internal static class ResiliencePipelines { - public static async Task ResiliencePipelineUsage() + public static async Task Usage() { var cancellationToken = CancellationToken.None; var httpClient = new HttpClient(); diff --git a/src/Snippets/Docs/ResilienceStrategies.cs b/src/Snippets/Docs/ResilienceStrategies.cs index 75324663f42..02fd87080d9 100644 --- a/src/Snippets/Docs/ResilienceStrategies.cs +++ b/src/Snippets/Docs/ResilienceStrategies.cs @@ -7,7 +7,7 @@ namespace Snippets.Docs; internal static class ResilienceStrategies { - public static async Task ResilienceStrategySample() + public static async Task Usage() { #region resilience-strategy-sample diff --git a/src/Snippets/Docs/Retry.cs b/src/Snippets/Docs/Retry.cs index 7b6b3e37d1b..e1055cf60f9 100644 --- a/src/Snippets/Docs/Retry.cs +++ b/src/Snippets/Docs/Retry.cs @@ -8,7 +8,7 @@ namespace Snippets.Docs; internal static class Retry { - public static void RetryExample() + public static void Usage() { #region retry diff --git a/src/Snippets/Docs/Timeout.cs b/src/Snippets/Docs/Timeout.cs index a2796740354..0a963014cc7 100644 --- a/src/Snippets/Docs/Timeout.cs +++ b/src/Snippets/Docs/Timeout.cs @@ -7,7 +7,7 @@ namespace Snippets.Docs; internal static class Timeout { - public static async Task TimeoutExample() + public static async Task Usage() { #region timeout From 517127f8daffe3d10ef6987b52f6a282b16a80b1 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Wed, 6 Sep 2023 13:24:20 +0200 Subject: [PATCH 3/9] PR comments --- README_V8.md | 73 +++++++-------------- docs/dependency-injection.md | 2 +- docs/extensiblity.md | 2 +- docs/general.md | 6 +- docs/resilience-context.md | 55 ++++++++++------ docs/resilience-pipeline-registry.md | 2 +- docs/resilience-pipelines.md | 14 ++-- docs/resilience-strategies.md | 4 +- docs/telemetry.md | 2 +- src/Polly.Core/README.md | 2 +- src/Snippets/Docs/CircuitBreaker.cs | 24 ++----- src/Snippets/Docs/Fallback.cs | 7 +- src/Snippets/Docs/Hedging.cs | 8 +-- src/Snippets/Docs/RateLimiter.cs | 2 +- src/Snippets/Docs/Readme.cs | 2 +- src/Snippets/Docs/ResilienceContextUsage.cs | 53 +++++++++------ 16 files changed, 122 insertions(+), 136 deletions(-) diff --git a/README_V8.md b/README_V8.md index 71c6a1c963a..0ee88db73c7 100644 --- a/README_V8.md +++ b/README_V8.md @@ -66,13 +66,13 @@ await pipeline.ExecuteAsync(async token => ### Dependency injection -If you prefer to define resilience pipelines using [`IServiceCollection`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.iservicecollection), you'll need to install the [Polly.Extensions](https://www.nuget.org/packages/Polly.Extensions/) package: +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 as shown: +You can then define your resilience pipeline using the `AddResiliencePipeline(...)` extension method as shown: ```cs @@ -92,7 +92,7 @@ IServiceProvider serviceProvider = services.BuildServiceProvider(); // Retrieve ResiliencePipelineProvider that caches and dynamically creates the resilience pipelines var pipelineProvider = serviceProvider.GetRequiredService>(); -// Retrieve resilience pipeline by the string-based name +// Retrieve resilience pipeline using the name it was registered with ResiliencePipeline pipeline = pipelineProvider.GetPipeline("my-pipeline"); // Execute the pipeline @@ -110,16 +110,16 @@ Polly provides a variety of resilience strategies. Alongside the comprehensive g 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 take pro-active actions to cancel or reject the execution of callbacks (e.g., using a rate limiter or a timeout resilience 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?| +| 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. | +|**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. | +|**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. @@ -209,22 +209,16 @@ new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions ``` -If all retries fail, a retry strategy rethrows the final exception back to the calling code. For more depth visit [retry strategy documentation](https://github.com/App-vNext/Polly/wiki/Retry). +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 -// Use the default Circuit Breaker options with pre-configured settings: -// -// FailureRatio: 0.1 — Opens the circuit if 10% of the calls fail -// BreakDuration: 30 seconds — Duration the circuit stays open before another try -// MinimumThroughput: 100 — Minimum number of calls required before the circuit can break -// SamplingDuration: 30 seconds — Time window for monitoring calls before allowing the circuit to break -// ShouldHandle: Handles all exceptions except for OperationCanceledException +// Add circuit breaker with default options. new ResiliencePipelineBuilder().AddCircuitBreaker(new CircuitBreakerStrategyOptions()); -// Configure custom settings for the Circuit Breaker: +// 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. @@ -250,10 +244,7 @@ new ResiliencePipelineBuilder() var stateProvider = new CircuitBreakerStateProvider(); new ResiliencePipelineBuilder() - .AddCircuitBreaker(new() - { - StateProvider = stateProvider - }) + .AddCircuitBreaker(new() { StateProvider = stateProvider }) .Build(); /* @@ -267,16 +258,13 @@ CircuitState.Isolated - Circuit is manually held open; actions are blocked. var manualControl = new CircuitBreakerManualControl(); new ResiliencePipelineBuilder() - .AddCircuitBreaker(new() - { - ManualControl = manualControl - }) + .AddCircuitBreaker(new() { ManualControl = manualControl }) .Build(); // Manually isolate a circuit, e.g., to isolate a downstream service. await manualControl.IsolateAsync(); -// Manually close the circuit to accept actions again. +// Manually close the circuit to allow actions to be executed again. await manualControl.CloseAsync(); ``` @@ -288,8 +276,8 @@ The Circuit Breaker strategy prevents execution by throwing a `BrokenCircuitExce For more insights on the Circuit Breaker pattern, you can visit: -- [Making the Netflix API More Resilient](http://techblog.netflix.com/2011/12/making-netflix-api-more-resilient.html) -- [Circuit Breaker by Martin Fowler](http://martinfowler.com/bliki/CircuitBreaker.html) +- [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) @@ -297,17 +285,14 @@ For more insights on the Circuit Breaker pattern, you can visit: ```cs -// Use a substitute value if an operation fails. +// 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 => - { - return Outcome.FromResultAsValueTask(UserAvatar.Blank); - } + FallbackAction = args => Outcome.FromResultAsValueTask(UserAvatar.Blank) }); // Use a dynamically generated value if an operation fails. @@ -351,14 +336,12 @@ For more details, refer to the [Fallback documentation](https://github.com/App-v ```cs -// Add a default hedging strategy that retries once if the previous -// execution didn't complete within 2 seconds or failed due to an exception. +// 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 a 500 Internal Server Error. +// 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 { @@ -383,7 +366,7 @@ new ResiliencePipelineBuilder() { OnHedging = args => { - Console.WriteLine("OnHedging: Attempt number {0}", args.AttemptNumber); + Console.WriteLine($"OnHedging: Attempt number {args.AttemptNumber}"); return default; } }); @@ -394,7 +377,7 @@ If all hedged attempts fail, the hedging strategy will either re-throw the last ### Timeout -The timeout resilience strategy assumes delegates you execute support [co-operative cancellation](https://msdn.microsoft.com/en-us/library/dd997364.aspx). You must use `Execute/Async(...)` overloads taking a `CancellationToken`, and the executed delegate must honor that `CancellationToken`. +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 @@ -459,7 +442,7 @@ Timeout strategies throw `TimeoutRejectedException` when a timeout occurs. For m ```cs -// Create a rate limiter with default options that limit to 1000 concurrent executions and no queue. +// Add rate limiter with default options. new ResiliencePipelineBuilder() .AddRateLimiter(new RateLimiterStrategyOptions()); @@ -532,12 +515,6 @@ Rate limiter strategy throws `RateLimiterRejectedException` if execution is reje To learn more about Polly, visit the [documentation](docs/README.md) or check out the [samples](#samples). -## Release notes - -- The [changelog](https://github.com/App-vNext/Polly/blob/main/CHANGELOG.md) describes changes by release. -- We tag Pull Requests and Issues with [milestones](https://github.com/App-vNext/Polly/milestones) which match to NuGet package release numbers. -- Breaking changes are called out in the wiki ([v7](https://github.com/App-vNext/Polly/wiki/Polly-v7-breaking-changes); [v6](https://github.com/App-vNext/Polly/wiki/Polly-v6-breaking-changes)) with simple notes on any necessary steps to upgrade. - ## Samples - [Samples](samples/README.md): Samples in this repository that serve as an introduction to Polly. diff --git a/docs/dependency-injection.md b/docs/dependency-injection.md index 64e9c91009a..829ed5afae2 100644 --- a/docs/dependency-injection.md +++ b/docs/dependency-injection.md @@ -1,3 +1,3 @@ # Dependency Injection -TODO +🚧 This documentation is being written as part of the Polly v8 release. diff --git a/docs/extensiblity.md b/docs/extensiblity.md index 6b7b32c3ac5..62ac8e12090 100644 --- a/docs/extensiblity.md +++ b/docs/extensiblity.md @@ -1,3 +1,3 @@ # Extensibility -TODO +🚧 This documentation is being written as part of the Polly v8 release. diff --git a/docs/general.md b/docs/general.md index 02ee077d4dc..59ef8c80e2b 100644 --- a/docs/general.md +++ b/docs/general.md @@ -2,7 +2,7 @@ ## Supported targets -Polly targets .NET Standard 2.0+ ([coverage](https://docs.microsoft.com/en-us/dotnet/standard/net-standard#net-implementation-support): .NET Core 2.0+, .NET Core 3.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. +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. @@ -42,7 +42,7 @@ The `CancellationToken` you pass to the `ExecuteAsync(...)` method serves multip - 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. -- Consistent with the .NET Base Class Library's behavior in `Task.Run(...)`, if the cancellation token is cancelled before execution begins, the user-defined delegate will not execute at all. +- 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 @@ -65,4 +65,4 @@ await pipeline.ExecuteAsync( 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 magically 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. +> 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 index bc48d4e377e..3dc70211d83 100644 --- a/docs/resilience-context.md +++ b/docs/resilience-context.md @@ -19,20 +19,18 @@ Below is an example demonstrating how to work with `ResilienceContext`: ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken); // Attach custom data to the context -var key1 = new ResiliencePropertyKey("my-key-1"); -var key2 = new ResiliencePropertyKey("my-key-2"); -context.Properties.Set(key1, "my-data"); -context.Properties.Set(key2, 123); +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 = args => + OnRetry = static args => { // Retrieve custom data from the context, if available - if (args.Context.Properties.TryGetValue(key1, out var data)) + if (args.Context.Properties.TryGetValue(MyResilienceKeys.Key1, out var data)) { Console.WriteLine("OnRetry, Custom Data: {0}", data); } @@ -55,6 +53,19 @@ 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 @@ -68,18 +79,24 @@ The `ResilienceContextPool` offers several `Get` methods. These methods not only // Retrieve a context with a cancellation token ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken); -// 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 - -// Return the context to the pool -ResilienceContextPool.Shared.Return(context); +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 index 15dfaf64631..461119df416 100644 --- a/docs/resilience-pipeline-registry.md +++ b/docs/resilience-pipeline-registry.md @@ -12,4 +12,4 @@ The `ResiliencePipelineRegistry` is a generic class that provides the foll ## Usage -TODO +🚧 This documentation is being written as part of the Polly v8 release. diff --git a/docs/resilience-pipelines.md b/docs/resilience-pipelines.md index 768eddb6c9e..3c4ccbe5ba2 100644 --- a/docs/resilience-pipelines.md +++ b/docs/resilience-pipelines.md @@ -4,7 +4,7 @@ The `ResiliencePipeline` allows executing arbitrary user-provided callbacks. It ## Usage -The `ResiliencePipeline` allow executing various synchronous and asynchronous user-provided callbacks as seen in the examples bellow: +The `ResiliencePipeline` allow executing various synchronous and asynchronous user-provided callbacks as seen in the examples below: ```cs @@ -62,7 +62,7 @@ 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/registry.md-TODO) 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. +- 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 @@ -78,22 +78,18 @@ public static void ConfigureMyPipelines(IServiceCollection services) ``` -> [!NOTE] -> In certain scenarios, such as testing, you may want to use the ResiliencePipeline.Empty instance. This instance executes the callback without applying any additional logic. - ## 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. +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. +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 @@ -137,4 +133,4 @@ else ``` -Utilize `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. +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 index e790fd0fd4e..21d3c311dd4 100644 --- a/docs/resilience-strategies.md +++ b/docs/resilience-strategies.md @@ -31,7 +31,7 @@ ResiliencePipeline pipeline = new ResiliencePipelineBuilder() 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 bellow: +This is demonstrated below: ```cs @@ -61,5 +61,5 @@ options.ShouldHandle = args => args.Outcome switch Some additional notes from the preceding example: - `PredicateBuilder` is a utility API designed to make configuring predicates easier. -- `PredicateResult.True()` is a shorthand for `new ValueTask(true)`. +- `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/telemetry.md b/docs/telemetry.md index ecb467b8800..814a30bd3a7 100644 --- a/docs/telemetry.md +++ b/docs/telemetry.md @@ -1,3 +1,3 @@ # Telemetry -TODO +🚧 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 9f55e357ea6..7fad113059b 100644 --- a/src/Polly.Core/README.md +++ b/src/Polly.Core/README.md @@ -187,7 +187,7 @@ Recommended signatures for these delegates are: ### Generators - `Func, ValueTask>` (Reactive) -- `Func>` (Non-Reactive) +- `Func>` (Proactive) 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 index f0385fbd1d5..2fb66df26e2 100644 --- a/src/Snippets/Docs/CircuitBreaker.cs +++ b/src/Snippets/Docs/CircuitBreaker.cs @@ -6,24 +6,16 @@ namespace Snippets.Docs; -#pragma warning disable S103 // Lines should not be too long - internal static class CircuitBreaker { public static async Task Usage() { #region circuit-breaker - // Use the default Circuit Breaker options with pre-configured settings: - // - // FailureRatio: 0.1 — Opens the circuit if 10% of the calls fail - // BreakDuration: 30 seconds — Duration the circuit stays open before another try - // MinimumThroughput: 100 — Minimum number of calls required before the circuit can break - // SamplingDuration: 30 seconds — Time window for monitoring calls before allowing the circuit to break - // ShouldHandle: Handles all exceptions except for OperationCanceledException + // Add circuit breaker with default options. new ResiliencePipelineBuilder().AddCircuitBreaker(new CircuitBreakerStrategyOptions()); - // Configure custom settings for the Circuit Breaker: + // 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. @@ -49,10 +41,7 @@ public static async Task Usage() var stateProvider = new CircuitBreakerStateProvider(); new ResiliencePipelineBuilder() - .AddCircuitBreaker(new() - { - StateProvider = stateProvider - }) + .AddCircuitBreaker(new() { StateProvider = stateProvider }) .Build(); /* @@ -66,16 +55,13 @@ public static async Task Usage() var manualControl = new CircuitBreakerManualControl(); new ResiliencePipelineBuilder() - .AddCircuitBreaker(new() - { - ManualControl = manualControl - }) + .AddCircuitBreaker(new() { ManualControl = manualControl }) .Build(); // Manually isolate a circuit, e.g., to isolate a downstream service. await manualControl.IsolateAsync(); - // Manually close the circuit to accept actions again. + // 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 index 63953400e28..3fc375f38b8 100644 --- a/src/Snippets/Docs/Fallback.cs +++ b/src/Snippets/Docs/Fallback.cs @@ -10,17 +10,14 @@ public static void Usage() { #region fallback - // Use a substitute value if an operation fails. + // 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 => - { - return Outcome.FromResultAsValueTask(UserAvatar.Blank); - } + FallbackAction = args => Outcome.FromResultAsValueTask(UserAvatar.Blank) }); // Use a dynamically generated value if an operation fails. diff --git a/src/Snippets/Docs/Hedging.cs b/src/Snippets/Docs/Hedging.cs index 0ef98244758..ad27ca58290 100644 --- a/src/Snippets/Docs/Hedging.cs +++ b/src/Snippets/Docs/Hedging.cs @@ -12,14 +12,12 @@ public static void Usage() { #region hedging - // Add a default hedging strategy that retries once if the previous - // execution didn't complete within 2 seconds or failed due to an exception. + // 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 a 500 Internal Server Error. + // 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 { @@ -44,7 +42,7 @@ public static void Usage() { OnHedging = args => { - Console.WriteLine("OnHedging: Attempt number {0}", args.AttemptNumber); + Console.WriteLine($"OnHedging: Attempt number {args.AttemptNumber}"); return default; } }); diff --git a/src/Snippets/Docs/RateLimiter.cs b/src/Snippets/Docs/RateLimiter.cs index ea169297ab0..c46886007be 100644 --- a/src/Snippets/Docs/RateLimiter.cs +++ b/src/Snippets/Docs/RateLimiter.cs @@ -10,7 +10,7 @@ public static void Usage() { #region rate-limiter - // Create a rate limiter with default options that limit to 1000 concurrent executions and no queue. + // Add rate limiter with default options. new ResiliencePipelineBuilder() .AddRateLimiter(new RateLimiterStrategyOptions()); diff --git a/src/Snippets/Docs/Readme.cs b/src/Snippets/Docs/Readme.cs index 1f518bfaa67..17cbc6afb84 100644 --- a/src/Snippets/Docs/Readme.cs +++ b/src/Snippets/Docs/Readme.cs @@ -55,7 +55,7 @@ public static async Task QuickStartDi() // Retrieve ResiliencePipelineProvider that caches and dynamically creates the resilience pipelines var pipelineProvider = serviceProvider.GetRequiredService>(); - // Retrieve resilience pipeline by the string-based name + // Retrieve resilience pipeline using the name it was registered with ResiliencePipeline pipeline = pipelineProvider.GetPipeline("my-pipeline"); // Execute the pipeline diff --git a/src/Snippets/Docs/ResilienceContextUsage.cs b/src/Snippets/Docs/ResilienceContextUsage.cs index 66c0f33d64d..f89130a0881 100644 --- a/src/Snippets/Docs/ResilienceContextUsage.cs +++ b/src/Snippets/Docs/ResilienceContextUsage.cs @@ -4,6 +4,17 @@ 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; @@ -14,20 +25,18 @@ public static async Task Usage() ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken); // Attach custom data to the context - var key1 = new ResiliencePropertyKey("my-key-1"); - var key2 = new ResiliencePropertyKey("my-key-2"); - context.Properties.Set(key1, "my-data"); - context.Properties.Set(key2, 123); + 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 = args => + OnRetry = static args => { // Retrieve custom data from the context, if available - if (args.Context.Properties.TryGetValue(key1, out var data)) + if (args.Context.Properties.TryGetValue(MyResilienceKeys.Key1, out var data)) { Console.WriteLine("OnRetry, Custom Data: {0}", data); } @@ -60,19 +69,25 @@ public static async Task Pooling() // Retrieve a context with a cancellation token ResilienceContext context = ResilienceContextPool.Shared.Get(cancellationToken); - // 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 - - // Return the context to the pool - ResilienceContextPool.Shared.Return(context); + 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 } From 2565b9033c27fe742b16813772b86f1aa368ac61 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Wed, 6 Sep 2023 13:25:38 +0200 Subject: [PATCH 4/9] cleanup --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 103992658d7..6b049870656 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,6 +18,6 @@ If you're already familiar with the [basic features](../README.md) of Polly, del - [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 (older Polly versions) +## Topics (previous Polly versions) - [Extensibility (v7)](v7/extensibility.md): Learn how you can extend Polly with new policies. From 484d29bcb6ea29c098f730afe17566a535f3aed9 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Wed, 6 Sep 2023 13:34:03 +0200 Subject: [PATCH 5/9] Cleanup --- README_V8.md | 5 +---- src/Snippets/Docs/Readme.cs | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/README_V8.md b/README_V8.md index 0ee88db73c7..367c60abd5a 100644 --- a/README_V8.md +++ b/README_V8.md @@ -44,10 +44,7 @@ You can create a `ResiliencePipeline` using the `ResiliencePipelineBuilder` clas // Create a instance of builder that exposes various extensions for adding resilience strategies var builder = new ResiliencePipelineBuilder(); -// Add retry using the default options: -// - 3 retry attempts -// - 1-second delay between retries -// - Handles all exceptions except OperationCanceledException +// Add retry using the default options builder.AddRetry(new RetryStrategyOptions()); // Add 10 second timeout diff --git a/src/Snippets/Docs/Readme.cs b/src/Snippets/Docs/Readme.cs index 17cbc6afb84..b0b2d1805b1 100644 --- a/src/Snippets/Docs/Readme.cs +++ b/src/Snippets/Docs/Readme.cs @@ -14,10 +14,7 @@ public static async Task QuickStart() // Create a instance of builder that exposes various extensions for adding resilience strategies var builder = new ResiliencePipelineBuilder(); - // Add retry using the default options: - // - 3 retry attempts - // - 1-second delay between retries - // - Handles all exceptions except OperationCanceledException + // Add retry using the default options builder.AddRetry(new RetryStrategyOptions()); // Add 10 second timeout From 9db41d47835fb05599cd46eae1cbf3c7ba60d18e Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Wed, 6 Sep 2023 13:36:16 +0200 Subject: [PATCH 6/9] Cleanup --- README_V8.md | 10 ++-------- src/Snippets/Docs/Retry.cs | 8 +------- src/Snippets/Docs/Timeout.cs | 2 +- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/README_V8.md b/README_V8.md index 367c60abd5a..6589a0673f1 100644 --- a/README_V8.md +++ b/README_V8.md @@ -124,13 +124,7 @@ Visit [resilience strategies](docs/resilience-strategies.md) docs to explore how ```cs -// To use the default retry options with pre-configured settings: -// -// MaxRetryAttempts: 3, -// Delay: 2 seconds, -// Backoff Type: Constant, -// UseJitter: false, -// ShouldHandle: Handles all exceptions except OperationCanceledException +// Add retry using the default options new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions()); // For instant retries with no delay @@ -378,7 +372,7 @@ The timeout resilience strategy assumes delegates you execute support [co-operat ```cs -// To add a default 30-second timeout +// To add timeout using the default options new ResiliencePipelineBuilder() .AddTimeout(new TimeoutStrategyOptions()); diff --git a/src/Snippets/Docs/Retry.cs b/src/Snippets/Docs/Retry.cs index e1055cf60f9..9d1fca8ac0c 100644 --- a/src/Snippets/Docs/Retry.cs +++ b/src/Snippets/Docs/Retry.cs @@ -12,13 +12,7 @@ public static void Usage() { #region retry - // To use the default retry options with pre-configured settings: - // - // MaxRetryAttempts: 3, - // Delay: 2 seconds, - // Backoff Type: Constant, - // UseJitter: false, - // ShouldHandle: Handles all exceptions except OperationCanceledException + // Add retry using the default options new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions()); // For instant retries with no delay diff --git a/src/Snippets/Docs/Timeout.cs b/src/Snippets/Docs/Timeout.cs index 0a963014cc7..90b7270d83d 100644 --- a/src/Snippets/Docs/Timeout.cs +++ b/src/Snippets/Docs/Timeout.cs @@ -11,7 +11,7 @@ public static async Task Usage() { #region timeout - // To add a default 30-second timeout + // To add timeout using the default options new ResiliencePipelineBuilder() .AddTimeout(new TimeoutStrategyOptions()); From ccd8e74e722074b8304f5a02932d00dd5795eabe Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Wed, 6 Sep 2023 13:38:27 +0200 Subject: [PATCH 7/9] Add missing italics --- README_V8.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README_V8.md b/README_V8.md index 6589a0673f1..90b2c9d2c7a 100644 --- a/README_V8.md +++ b/README_V8.md @@ -112,10 +112,10 @@ Polly categorizes resilience strategies into two main groups: | 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. | +|**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. | +|**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. From e8333e85e2e351643c3e57a525904d2110831f03 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Wed, 6 Sep 2023 14:09:45 +0200 Subject: [PATCH 8/9] Add banner to some docs --- docs/general.md | 3 +++ docs/resilience-context.md | 3 +++ docs/resilience-pipeline-registry.md | 3 +++ docs/resilience-pipelines.md | 3 +++ docs/resilience-strategies.md | 3 +++ 5 files changed, 15 insertions(+) diff --git a/docs/general.md b/docs/general.md index 59ef8c80e2b..6a243973a80 100644 --- a/docs/general.md +++ b/docs/general.md @@ -1,5 +1,8 @@ # 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. diff --git a/docs/resilience-context.md b/docs/resilience-context.md index 3dc70211d83..b6b62b2c6f8 100644 --- a/docs/resilience-context.md +++ b/docs/resilience-context.md @@ -1,5 +1,8 @@ # 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: diff --git a/docs/resilience-pipeline-registry.md b/docs/resilience-pipeline-registry.md index 461119df416..06e476142f4 100644 --- a/docs/resilience-pipeline-registry.md +++ b/docs/resilience-pipeline-registry.md @@ -1,5 +1,8 @@ # 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. diff --git a/docs/resilience-pipelines.md b/docs/resilience-pipelines.md index 3c4ccbe5ba2..d1d075b8192 100644 --- a/docs/resilience-pipelines.md +++ b/docs/resilience-pipelines.md @@ -1,5 +1,8 @@ # 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 diff --git a/docs/resilience-strategies.md b/docs/resilience-strategies.md index 21d3c311dd4..40a246c0c61 100644 --- a/docs/resilience-strategies.md +++ b/docs/resilience-strategies.md @@ -1,5 +1,8 @@ # 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 From bee8d1780c5231978b2383b8da13e88324d47df5 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Wed, 6 Sep 2023 14:10:26 +0200 Subject: [PATCH 9/9] Cleanup --- src/Snippets/Docs/Hedging.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Snippets/Docs/Hedging.cs b/src/Snippets/Docs/Hedging.cs index ad27ca58290..09e2723c1d2 100644 --- a/src/Snippets/Docs/Hedging.cs +++ b/src/Snippets/Docs/Hedging.cs @@ -17,7 +17,7 @@ public static void Usage() .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. + // 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 {