From 7d6f669ba9e3aad3d7ba673552aa5109c6045dfb Mon Sep 17 00:00:00 2001 From: martintmk <103487740+martintmk@users.noreply.github.com> Date: Wed, 6 Sep 2023 16:19:16 +0200 Subject: [PATCH] [Docs] Add docs for individual resilience strategies (#1553) --- README_V8.md | 90 ++++----------- docs/README.md | 9 ++ docs/strategies/circuit-breaker.md | 99 +++++++++++++++++ docs/strategies/fallback.md | 67 +++++++++++ docs/strategies/hedging.md | 62 +++++++++++ docs/strategies/rate-limiter.md | 92 +++++++++++++++ docs/strategies/retry.md | 105 ++++++++++++++++++ docs/strategies/timeout.md | 80 +++++++++++++ .../RateLimiterStrategyOptions.cs | 18 +-- src/Snippets/Docs/CircuitBreaker.cs | 1 + src/Snippets/Docs/Fallback.cs | 2 +- src/Snippets/Docs/Hedging.cs | 1 + src/Snippets/Docs/RateLimiter.cs | 1 + src/Snippets/Docs/Retry.cs | 3 +- src/Snippets/Docs/Timeout.cs | 3 +- .../Controller/ScheduledTaskExecutorTests.cs | 2 +- 16 files changed, 553 insertions(+), 82 deletions(-) create mode 100644 docs/strategies/circuit-breaker.md create mode 100644 docs/strategies/fallback.md create mode 100644 docs/strategies/hedging.md create mode 100644 docs/strategies/rate-limiter.md create mode 100644 docs/strategies/retry.md create mode 100644 docs/strategies/timeout.md diff --git a/README_V8.md b/README_V8.md index 90b2c9d2c7..631b708285 100644 --- a/README_V8.md +++ b/README_V8.md @@ -111,12 +111,12 @@ 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. | -|**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. | +|**Retry**
(strategy family)
([quickstart](#retry) ; [deep](docs/strategies/retry.md)) |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](docs/strategies/circuit-breaker.md))|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](docs/strategies/timeout.md))|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](docs/strategies/rate-limiter.md))|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](docs/strategies/fallback.md))|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](docs/strategies/hedging.md))|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. @@ -124,7 +124,8 @@ Visit [resilience strategies](docs/resilience-strategies.md) docs to explore how ```cs -// Add retry using the default options +// Add retry using the default options. +// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/retry.md#defaults for default values. new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions()); // For instant retries with no delay @@ -200,13 +201,14 @@ new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions ``` -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). +If all retries fail, a retry strategy rethrows the final exception back to the calling code. For more details visit the [retry strategy documentation](docs/strategies/retry.md). ### Circuit Breaker ```cs // Add circuit breaker with default options. +// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/circuit-breaker.md#defaults for default values. new ResiliencePipelineBuilder().AddCircuitBreaker(new CircuitBreakerStrategyOptions()); // Add circuit breaker with customized options: @@ -260,23 +262,13 @@ await manualControl.CloseAsync(); ``` -The Circuit Breaker strategy prevents execution by throwing a `BrokenCircuitException` when the circuit is open. For more details, refer to the [Circuit-Breaker documentation on GitHub](https://github.com/App-vNext/Polly/wiki/Advanced-Circuit-Breaker). - -> [!NOTE] -> Be aware that the Circuit Breaker strategy [rethrows all exceptions](https://github.com/App-vNext/Polly/wiki/Circuit-Breaker#exception-handling), including those that are handled. A Circuit Breaker's role is to monitor faults and break the circuit when a certain threshold is reached; it does not manage retries. Combine the Circuit Breaker with a Retry strategy if needed. - -For more insights on the Circuit Breaker pattern, you can visit: - -- [Making the Netflix API More Resilient](https://techblog.netflix.com/2011/12/making-netflix-api-more-resilient.html) -- [Circuit Breaker by Martin Fowler](https://martinfowler.com/bliki/CircuitBreaker.html) -- [Circuit Breaker Pattern by Microsoft](https://msdn.microsoft.com/en-us/library/dn589784.aspx) -- [Original Circuit Breaking Article](https://web.archive.org/web/20160106203951/http://thatextramile.be/blog/2008/05/the-circuit-breaker) +For more details, refer to the [Circuit-Breaker documentation](docs/strategies/circuit-breaker.md). ### Fallback ```cs -// Use a fallback/substitute value if an operation fails. +// Add a fallback/substitute value if an operation fails. new ResiliencePipelineBuilder() .AddFallback(new FallbackStrategyOptions { @@ -321,18 +313,19 @@ new ResiliencePipelineBuilder() ``` -For more details, refer to the [Fallback documentation](https://github.com/App-vNext/Polly/wiki/Fallback). +For more details, refer to the [Fallback documentation](docs/strategies/fallback.md). ### Hedging ```cs // Add hedging with default options. +// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/hedging.md#defaults for default values. new ResiliencePipelineBuilder() .AddHedging(new HedgingStrategyOptions()); // Add a customized hedging strategy that retries up to 3 times if the execution -// takes longer than 1 second or if it fails due to an exception or returns an HTTP 500 Internal Server Error. +// 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 { @@ -364,7 +357,7 @@ new ResiliencePipelineBuilder() ``` -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). +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/strategies/hedging.md). ### Timeout @@ -372,7 +365,8 @@ The timeout resilience strategy assumes delegates you execute support [co-operat ```cs -// To add timeout using the default options +// Add timeout using the default options. +// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/timeout.md#defaults for default values. new ResiliencePipelineBuilder() .AddTimeout(new TimeoutStrategyOptions()); @@ -409,31 +403,14 @@ new ResiliencePipelineBuilder() ``` -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). +Timeout strategies throw `TimeoutRejectedException` when a timeout occurs. For more details see [Timeout strategy documentation](docs/strategies/timeout.md). ### Rate Limiter ```cs // Add rate limiter with default options. +// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/rate-limiter.md#defaults for default values. new ResiliencePipelineBuilder() .AddRateLimiter(new RateLimiterStrategyOptions()); @@ -475,32 +452,7 @@ new ResiliencePipelineBuilder() ``` -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). +Rate limiter strategy throws `RateLimiterRejectedException` if execution is rejected. For more details see [Rate Limiter strategy documentation](docs/strategies/rate-limiter.md). ## Next steps diff --git a/docs/README.md b/docs/README.md index 6b04987065..c05f917518 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,6 +2,15 @@ If you're already familiar with the [basic features](../README.md) of Polly, delve deeper into its advanced functionalities here. +## Resilience strategies + +- [Retry](strategies/retry.md): Allows for the automated re-execution of failed operations according to predefined conditions. +- [Circuit Breaker](strategies/circuit-breaker.md): Temporarily halts all operations when a defined threshold of failures is exceeded, in order to prevent further issues. +- [Fallback](strategies/fallback.md): Provides a backup value or executes an alternative action when the primary operation fails, ensuring graceful degradation. +- [Hedging](strategies/hedging.md): Initiates multiple identical operations in parallel when performance lags, and returns the result of the fastest-completing operation. +- [Timeout](strategies/timeout.md): Sets a maximum time limit for an operation to complete, preventing indefinite waiting. +- [Rate Limiter](strategies/rate-limiter.md): Regulates the frequency of operations to ensure they do not exceed a set rate, thereby maintaining system stability. + ## Topics - [General](general.md): General information about Polly. diff --git a/docs/strategies/circuit-breaker.md b/docs/strategies/circuit-breaker.md new file mode 100644 index 0000000000..a4a267db7e --- /dev/null +++ b/docs/strategies/circuit-breaker.md @@ -0,0 +1,99 @@ +# Circuit breaker resilience strategy + +## About + +- **Options**: + - [`CircuitBreakerStrategyOptions`](../../src/Polly.Core/CircuitBreaker/CircuitBreakerStrategyOptions.cs) + - [`CircuitBreakerStrategyOptions`](../../src/Polly.Core/CircuitBreaker/CircuitBreakerStrategyOptions.TResult.cs) +- **Extensions**: `AddCircuitBreaker` +- **Strategy Type**: Reactive +- **Exceptions**: + - `BrokenCircuitException`: Thrown when a circuit is broken and the action could not be executed. + - `IsolatedCircuitException`: Thrown when a circuit is isolated (held open) by manual override. + +> [!NOTE] +> Version 8 documentation for this strategy has not yet been migrated. For more information on circuit breaker concepts and behavior, refer to the [older documentation](https://github.com/App-vNext/Polly/wiki/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. + +## Usage + + +```cs +// Add circuit breaker with default options. +// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/circuit-breaker.md#defaults for default values. +new ResiliencePipelineBuilder().AddCircuitBreaker(new CircuitBreakerStrategyOptions()); + +// Add circuit breaker with customized options: +// +// The circuit will break if more than 50% of actions result in handled exceptions, +// within any 10-second sampling duration, and at least 8 actions are processed. +new ResiliencePipelineBuilder().AddCircuitBreaker(new CircuitBreakerStrategyOptions +{ + FailureRatio = 0.5, + SamplingDuration = TimeSpan.FromSeconds(10), + MinimumThroughput = 8, + BreakDuration = TimeSpan.FromSeconds(30), + ShouldHandle = new PredicateBuilder().Handle() +}); + +// Handle specific failed results for HttpResponseMessage: +new ResiliencePipelineBuilder() + .AddCircuitBreaker(new CircuitBreakerStrategyOptions + { + ShouldHandle = new PredicateBuilder() + .Handle() + .HandleResult(response => response.StatusCode == HttpStatusCode.InternalServerError) + }); + +// Monitor the circuit state, useful for health reporting: +var stateProvider = new CircuitBreakerStateProvider(); + +new ResiliencePipelineBuilder() + .AddCircuitBreaker(new() { StateProvider = stateProvider }) + .Build(); + +/* +CircuitState.Closed - Normal operation; actions are executed. +CircuitState.Open - Circuit is open; actions are blocked. +CircuitState.HalfOpen - Recovery state after break duration expires; actions are permitted. +CircuitState.Isolated - Circuit is manually held open; actions are blocked. +*/ + +// Manually control the Circuit Breaker state: +var manualControl = new CircuitBreakerManualControl(); + +new ResiliencePipelineBuilder() + .AddCircuitBreaker(new() { ManualControl = manualControl }) + .Build(); + +// Manually isolate a circuit, e.g., to isolate a downstream service. +await manualControl.IsolateAsync(); + +// Manually close the circuit to allow actions to be executed again. +await manualControl.CloseAsync(); +``` + + +## Defaults + +| Property | Default Value | Description | +| ------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `ShouldHandle` | Predicate that handles all exceptions except `OperationCanceledException`. | Specifies which results and exceptions are managed by the circuit breaker strategy. | +| `FailureRatio` | 0.1 | The ratio of failures to successes that will cause the circuit to break/open. | +| `MinimumThroughput` | 100 | The minimum number of actions that must occur in the circuit within a specific time slice. | +| `SamplingDuration` | 30 seconds | The time period over which failure ratios are calculated. | +| `BreakDuration` | 5 seconds | The time period for which the circuit will remain broken/open before attempting to reset. | +| `OnClosed` | `null` | Event triggered when the circuit transitions to the `Closed` state. | +| `OnOpened` | `null` | Event triggered when the circuit transitions to the `Opened` state. | +| `OnHalfOpened` | `null` | Event triggered when the circuit transitions to the `HalfOpened` state. | +| `ManualControl` | `null` | Allows for manual control to isolate or close the circuit. | +| `StateProvider` | `null` | Enables the retrieval of the current state of the circuit. | + +## Resources + +- [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) diff --git a/docs/strategies/fallback.md b/docs/strategies/fallback.md new file mode 100644 index 0000000000..fa51af2c58 --- /dev/null +++ b/docs/strategies/fallback.md @@ -0,0 +1,67 @@ +# Fallback resilience strategy + +## About + +- **Options**: [`FallbackStrategyOptions`](../../src/Polly.Core/Fallback/FallbackStrategyOptions.TResult.cs) +- **Extensions**: `AddFallback` +- **Strategy Type**: Reactive + +> [!NOTE] +> Version 8 documentation for this strategy has not yet been migrated. For more information on fallback concepts and behavior, refer to the [older documentation](https://github.com/App-vNext/Polly/wiki/Fallback). + +## Usage + + +```cs +// Add a fallback/substitute value if an operation fails. +new ResiliencePipelineBuilder() + .AddFallback(new FallbackStrategyOptions + { + ShouldHandle = new PredicateBuilder() + .Handle() + .HandleResult(r => r is null), + FallbackAction = args => Outcome.FromResultAsValueTask(UserAvatar.Blank) + }); + +// Use a dynamically generated value if an operation fails. +new ResiliencePipelineBuilder() + .AddFallback(new FallbackStrategyOptions + { + ShouldHandle = new PredicateBuilder() + .Handle() + .HandleResult(r => r is null), + FallbackAction = args => + { + var avatar = UserAvatar.GetRandomAvatar(); + return Outcome.FromResultAsValueTask(avatar); + } + }); + +// Use a default or dynamically generated value, and execute an additional action if the fallback is triggered. +new ResiliencePipelineBuilder() + .AddFallback(new FallbackStrategyOptions + { + ShouldHandle = new PredicateBuilder() + .Handle() + .HandleResult(r => r is null), + FallbackAction = args => + { + var avatar = UserAvatar.GetRandomAvatar(); + return Outcome.FromResultAsValueTask(UserAvatar.Blank); + }, + OnFallback = args => + { + // Add extra logic to be executed when the fallback is triggered, such as logging. + return default; // returns an empty ValueTask + } + }); +``` + + +## Defaults + +| Property | Default Value | Description | +| ---------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| `ShouldHandle` | Predicate that handles all exceptions except `OperationCanceledException`. | Predicate that determines what results and exceptions are handled by the fallback strategy. | +| `FallbackAction` | `Null`, **Required** | Fallback action to be executed. | +| `OnFallback` | `null` | Event that is raised when fallback happens. | diff --git a/docs/strategies/hedging.md b/docs/strategies/hedging.md new file mode 100644 index 0000000000..f2479f3eff --- /dev/null +++ b/docs/strategies/hedging.md @@ -0,0 +1,62 @@ +# Hedging resilience strategy + +## About + +- **Options**: [`HedgingStrategyOptions`](../../src/Polly.Core/Hedging/HedgingStrategyOptions.TResult.cs) +- **Extensions**: `AddHedging` +- **Strategy Type**: Reactive + +> 🚧 This documentation is being written as part of the Polly v8 release. + +## Usage + + +```cs +// Add hedging with default options. +// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/hedging.md#defaults for default values. +new ResiliencePipelineBuilder() + .AddHedging(new HedgingStrategyOptions()); + +// Add a customized hedging strategy that retries up to 3 times if the execution +// takes longer than 1 second or if it fails due to an exception or returns an HTTP 500 Internal Server Error. +new ResiliencePipelineBuilder() + .AddHedging(new HedgingStrategyOptions + { + ShouldHandle = new PredicateBuilder() + .Handle() + .HandleResult(response => response.StatusCode == HttpStatusCode.InternalServerError), + MaxHedgedAttempts = 3, + Delay = TimeSpan.FromSeconds(1), + ActionGenerator = args => + { + Console.WriteLine("Preparing to execute hedged action."); + + // Return a delegate function to invoke the original action with the action context. + // Optionally, you can also create a completely new action to be executed. + return () => args.Callback(args.ActionContext); + } + }); + +// Subscribe to hedging events. +new ResiliencePipelineBuilder() + .AddHedging(new HedgingStrategyOptions + { + OnHedging = args => + { + Console.WriteLine($"OnHedging: Attempt number {args.AttemptNumber}"); + return default; + } + }); +``` + + +## Defaults + +| Property | Default Value | Description | +| ------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| `ShouldHandle` | Predicate that handles all exceptions except `OperationCanceledException`. | Predicate that determines what results and exceptions are handled by the retry strategy. | +| `MaxHedgedAttempts` | 1 | The maximum number of hedged actions to use, in addition to the original action. | +| `Delay` | 2 seconds | The maximum waiting time before spawning a new hedged action. | +| `ActionGenerator` | Returns the original callback that was passed to the hedging strategy. | Generator that creates hedged actions. | +| `DelayGenerator` | `null` | Used for generating custom delays for hedging. | +| `OnHedging` | `null` | Event that is raised when a hedging is performed. | diff --git a/docs/strategies/rate-limiter.md b/docs/strategies/rate-limiter.md new file mode 100644 index 0000000000..3a08dd71a9 --- /dev/null +++ b/docs/strategies/rate-limiter.md @@ -0,0 +1,92 @@ +# Rate limiter resilience strategy + +## About + +- **Options**: [`RateLimiterStrategyOptions`](../../src/Polly.RateLimiting/RateLimiterStrategyOptions.cs) +- **Extensions**: `AddRateLimiter`, `AddConcurrencyLimiter` +- **Strategy Type**: Proactive +- **Exceptions**: + - `RateLimiterRejectedException`: Thrown when a rate limiter rejects an execution. +- **Package**: [Polly.RateLimiting](https://www.nuget.org/packages/Polly.RateLimiting) + +> 🚧 This documentation is being written as part of the Polly v8 release. + +## Usage + + +```cs +// Add rate limiter with default options. +// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/rate-limiter.md#defaults for default values. +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}"); + } +} +``` + + +## Defaults + +| Property | Default Value | Description | +| --------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| `RateLimiter` | `null` | Generator that creates a `RateLimitLease` for executions. | +| `DefaultRateLimiterOptions` | `PermitLimit` set to 1000 and `QueueLimit` set to 0. | The options for the default concurrency limiter that will be used when `RateLimiter` is `null`. | +| `OnRejected` | `null` | Event that is raised when the execution is rejected by the rate limiter. | diff --git a/docs/strategies/retry.md b/docs/strategies/retry.md new file mode 100644 index 0000000000..8ca731a909 --- /dev/null +++ b/docs/strategies/retry.md @@ -0,0 +1,105 @@ +# Retry resilience strategy + +## About + +- **Options**: + - [`RetryStrategyOptions`](../../src/Polly.Core/Retry/RetryStrategyOptions.cs) + - [`RetryStrategyOptions`](../../src/Polly.Core/Retry/RetryStrategyOptions.TResult.cs) +- **Extensions**: `AddRetry` +- **Strategy Type**: Reactive + +> [!NOTE] +> Version 8 documentation for this strategy has not yet been migrated. For more information on retry concepts and behavior, refer to the [older documentation](https://github.com/App-vNext/Polly/wiki/Retry). + +## Usage + + +```cs +// Add retry using the default options. +// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/retry.md#defaults for default values. +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, +}); +``` + + +## Defaults + +| Property | Default Value | Description | +| ------------------ | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| `ShouldHandle` | Predicate that handles all exceptions except `OperationCanceledException`. | Predicate that determines what results and exceptions are handled by the retry strategy. | +| `MaxRetryAttempts` | 3 | The maximum number of retries to use, in addition to the original call. | +| `Delay` | 2 seconds | The base delay between retries. | +| `BackoffType` | Constant | The type of the back-off used to generate the retry delay. | +| `UseJitter` | False | Allows adding jitter to retry delays. | +| `DelayGenerator` | `null` | Used for generating custom delays for retries. | +| `OnRetry` | `null` | Action executed when retry occurs. | diff --git a/docs/strategies/timeout.md b/docs/strategies/timeout.md new file mode 100644 index 0000000000..29b5d3facf --- /dev/null +++ b/docs/strategies/timeout.md @@ -0,0 +1,80 @@ +# Timeout resilience strategy + +## About + +- **Options**: [`TimeoutStrategyOptions`](../../src/Polly.Core/Timeout/TimeoutStrategyOptions.cs) +- **Extensions**: `AddTimeout` +- **Strategy Type**: Proactive +- **Exceptions**: + - `TimeoutRejectedException`: Thrown when a delegate executed through a timeout strategy does not complete before the timeout. + +> [!NOTE] +> Version 8 documentation for this strategy has not yet been migrated. For more information on timeout concepts and behavior, refer to the [older documentation](https://github.com/App-vNext/Polly/wiki/Timeout). + +## Usage + + +```cs +// Add timeout using the default options. +// See https://github.com/App-vNext/Polly/blob/main/docs/strategies/timeout.md#defaults for default values. +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); +``` + + +## Defaults + +| Property | Default Value | Description | +| ------------------ | ------------- | -------------------------------------------- | +| `Timeout` | 30 seconds | The default timeout used by the strategy. | +| `TimeoutGenerator` | `null` | Generates the timeout for a given execution. | +| `OnTimeout` | `null` | Event that is raised when timeout occurs. | diff --git a/src/Polly.RateLimiting/RateLimiterStrategyOptions.cs b/src/Polly.RateLimiting/RateLimiterStrategyOptions.cs index f35c83c84d..29ceb354de 100644 --- a/src/Polly.RateLimiting/RateLimiterStrategyOptions.cs +++ b/src/Polly.RateLimiting/RateLimiterStrategyOptions.cs @@ -13,6 +13,15 @@ public class RateLimiterStrategyOptions : ResilienceStrategyOptions /// public RateLimiterStrategyOptions() => Name = RateLimiterConstants.DefaultName; + /// + /// Gets or sets a rate limiter delegate that produces . + /// + /// + /// The default value is . If this property is , then the strategy + /// will use a created using . + /// + public Func>? RateLimiter { get; set; } + /// /// Gets or sets the default rate limiter options. /// @@ -37,13 +46,4 @@ public class RateLimiterStrategyOptions : ResilienceStrategyOptions /// The default value is . /// public Func? OnRejected { get; set; } - - /// - /// Gets or sets a rate limiter delegate that produces . - /// - /// - /// The default value is . If this property is , then the strategy - /// will use a created using . - /// - public Func>? RateLimiter { get; set; } } diff --git a/src/Snippets/Docs/CircuitBreaker.cs b/src/Snippets/Docs/CircuitBreaker.cs index 2fb66df26e..568d8bdafc 100644 --- a/src/Snippets/Docs/CircuitBreaker.cs +++ b/src/Snippets/Docs/CircuitBreaker.cs @@ -13,6 +13,7 @@ public static async Task Usage() #region circuit-breaker // Add circuit breaker with default options. + // See https://github.com/App-vNext/Polly/blob/main/docs/strategies/circuit-breaker.md#defaults for default values. new ResiliencePipelineBuilder().AddCircuitBreaker(new CircuitBreakerStrategyOptions()); // Add circuit breaker with customized options: diff --git a/src/Snippets/Docs/Fallback.cs b/src/Snippets/Docs/Fallback.cs index 3fc375f38b..cf9a670646 100644 --- a/src/Snippets/Docs/Fallback.cs +++ b/src/Snippets/Docs/Fallback.cs @@ -10,7 +10,7 @@ public static void Usage() { #region fallback - // Use a fallback/substitute value if an operation fails. + // Add a fallback/substitute value if an operation fails. new ResiliencePipelineBuilder() .AddFallback(new FallbackStrategyOptions { diff --git a/src/Snippets/Docs/Hedging.cs b/src/Snippets/Docs/Hedging.cs index 09e2723c1d..8e1ae8e58d 100644 --- a/src/Snippets/Docs/Hedging.cs +++ b/src/Snippets/Docs/Hedging.cs @@ -13,6 +13,7 @@ public static void Usage() #region hedging // Add hedging with default options. + // See https://github.com/App-vNext/Polly/blob/main/docs/strategies/hedging.md#defaults for default values. new ResiliencePipelineBuilder() .AddHedging(new HedgingStrategyOptions()); diff --git a/src/Snippets/Docs/RateLimiter.cs b/src/Snippets/Docs/RateLimiter.cs index c46886007b..2ac2cfbe02 100644 --- a/src/Snippets/Docs/RateLimiter.cs +++ b/src/Snippets/Docs/RateLimiter.cs @@ -11,6 +11,7 @@ public static void Usage() #region rate-limiter // Add rate limiter with default options. + // See https://github.com/App-vNext/Polly/blob/main/docs/strategies/rate-limiter.md#defaults for default values. new ResiliencePipelineBuilder() .AddRateLimiter(new RateLimiterStrategyOptions()); diff --git a/src/Snippets/Docs/Retry.cs b/src/Snippets/Docs/Retry.cs index 9d1fca8ac0..3973f8f491 100644 --- a/src/Snippets/Docs/Retry.cs +++ b/src/Snippets/Docs/Retry.cs @@ -12,7 +12,8 @@ public static void Usage() { #region retry - // Add retry using the default options + // Add retry using the default options. + // See https://github.com/App-vNext/Polly/blob/main/docs/strategies/retry.md#defaults for default values. 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 90b7270d83..929ecd22fa 100644 --- a/src/Snippets/Docs/Timeout.cs +++ b/src/Snippets/Docs/Timeout.cs @@ -11,7 +11,8 @@ public static async Task Usage() { #region timeout - // To add timeout using the default options + // Add timeout using the default options. + // See https://github.com/App-vNext/Polly/blob/main/docs/strategies/timeout.md#defaults for default values. new ResiliencePipelineBuilder() .AddTimeout(new TimeoutStrategyOptions()); diff --git a/test/Polly.Core.Tests/CircuitBreaker/Controller/ScheduledTaskExecutorTests.cs b/test/Polly.Core.Tests/CircuitBreaker/Controller/ScheduledTaskExecutorTests.cs index 9dc91d9b94..4b85dd4fc9 100644 --- a/test/Polly.Core.Tests/CircuitBreaker/Controller/ScheduledTaskExecutorTests.cs +++ b/test/Polly.Core.Tests/CircuitBreaker/Controller/ScheduledTaskExecutorTests.cs @@ -24,7 +24,7 @@ public async Task ScheduleTask_Success_EnsureExecuted() } [Fact] - public async Task ScheduleTask_OperationCancelledException_EnsureExecuted() + public async Task ScheduleTask_OperationCanceledException_EnsureExecuted() { using var scheduler = new ScheduledTaskExecutor(); scheduler.ScheduleTask(