diff --git a/.github/wordlist.txt b/.github/wordlist.txt index 0ddf7ccf1ce..794bc56c923 100644 --- a/.github/wordlist.txt +++ b/.github/wordlist.txt @@ -51,6 +51,7 @@ rethrow rethrows retryable reusability +runtime saas sdk serializers diff --git a/docs/strategies/circuit-breaker.md b/docs/strategies/circuit-breaker.md index fdd744c7f8c..fdd85fe78dc 100644 --- a/docs/strategies/circuit-breaker.md +++ b/docs/strategies/circuit-breaker.md @@ -2,17 +2,20 @@ ## About -- **Options**: +- **Option(s)**: - [`CircuitBreakerStrategyOptions`](xref:Polly.CircuitBreaker.CircuitBreakerStrategyOptions) - [`CircuitBreakerStrategyOptions`](xref:Polly.CircuitBreaker.CircuitBreakerStrategyOptions`1) -- **Extensions**: `AddCircuitBreaker` +- **Extension(s)**: + - `AddCircuitBreaker` - **Strategy Type**: Reactive -- **Exceptions**: +- **Exception(s)**: - `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. --- +The circuit breaker **reactive** resilience strategy shortcuts the execution if the underlying resource is detected as unhealthy. The detection process is done via sampling. If the sampled executions' failure-success ratio exceeds a predefined threshold then a circuit breaker will prevent any new executions by throwing a `BrokenCircuitException`. After a preset duration the circuit breaker performs a probe, because the assumption is that this period was enough for the resource to self-heal. Depending on the outcome of the probe, the circuit will either allow new executions or continue to block them. + > [!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. @@ -91,23 +94,62 @@ new ResiliencePipelineBuilder().AddCircuitBreaker(optionsSt ## 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. | -| `BreakDurationGenerator` | `null` | Enables adaptive adjustment of break duration based on the current state of the circuit. | -| `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. | +| Property | Default Value | Description | +|--------------------------|---------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| +| `ShouldHandle` | Any exceptions other than `OperationCanceledException`. | Defines a predicate to determine what results and/or exceptions are handled by the circuit breaker strategy. | +| `FailureRatio` | 0.1 | The failure-success ratio that will cause the circuit to break/open. `0.1` means 10% failed of all sampled executions. | +| `MinimumThroughput` | 100 | The minimum number of executions that must occur within the specified sampling duration. | +| `SamplingDuration` | 30 seconds | The time period over which the failure-success ratio is calculated. | +| `BreakDuration` | 5 seconds | Defines a **static** time period for which the circuit will remain broken/open before attempting to reset. | +| `BreakDurationGenerator` | `null` | This delegate allows you to **dynamically** calculate the break duration by utilizing information that is only available at runtime (like failure count). | +| `ManualControl` | `null` | If provided then the circuit's state can be manually controlled via a `CircuitBreakerManualControl` object. | +| `StateProvider` | `null` | If provided then the circuit's current state can be retrieved via a `CircuitBreakerStateProvider` object. | +| `OnClosed` | `null` | If provided then it will be invoked after the circuit transitions to either the `Closed` or `Isolated` states. | +| `OnOpened` | `null` | If provided then it will be invoked after the circuit transitions to the `Opened` state. | +| `OnHalfOpened` | `null` | If provided then it will be invoked after the circuit transitions to the `HalfOpened` state. | > [!NOTE] > If both `BreakDuration` and `BreakDurationGenerator` are specified then `BreakDuration` will be ignored. +--- + +> [!IMPORTANT] +> If the `MinimumThroughput` is not reached during the `SamplingDuration` then the `FailureRatio` is ignored. +> In other words, the circuit will not break even if all of the executions failed when their quantity is below the minimum throughput. + +## Telemetry + +The circuit breaker strategy reports the following telemetry events: + +| Event Name | Event Severity | When? | +|-----------------------|----------------|------------------------------------------------------------| +| `OnCircuitClosed` | `Information` | Just before the strategy calls the `OnClosed` delegate | +| `OnCircuitOpened` | `Error` | Just before the strategy calls the `OnOpened` delegate | +| `OnCircuitHalfOpened` | `Warning` | Just before the strategy calls the `OnHalfOpened` delegate | + +Here are some sample events: + +```none +Resilience event occurred. EventName: 'OnCircuitOpened', Source: 'MyPipeline/MyPipelineInstance/MyCircuitBreakerStrategy', Operation Key: 'MyCircuitedOperation', Result: 'Exception of type 'CustomException' was thrown.' + CustomException: Exception of type 'CustomException' was thrown. + at Program.<>c.<
b__0_1>d.MoveNext() + ... + at Polly.ResiliencePipeline.<>c__8`1.<b__8_0>d.MoveNext() in /_/src/Polly.Core/ResiliencePipeline.AsyncT.cs:line 95 + +Resilience event occurred. EventName: 'OnCircuitHalfOpened', Source: 'MyPipeline/MyPipelineInstance/MyCircuitBreakerStrategy', Operation Key: 'MyCircuitedOperation', Result: '' + +Resilience event occurred. EventName: 'OnCircuitClosed', Source: 'MyPipeline/MyPipelineInstance/MyCircuitBreakerStrategy', Operation Key: 'MyCircuitedOperation', Result: '42' +``` + +> [!NOTE] +> Please note that the `OnCircuitXYZ` telemetry events will be reported **only if** the circuit breaker strategy transitions from one state into another. +> +> Remember in case of `ManualControl` the `OnCircuitHalfOpened` telemetry event will not be emitted. +> +> Also the `Result` will be **always empty** for the `OnCircuitHalfOpened` telemetry event. + +For further information please check out the [telemetry page](https://www.pollydocs.org/advanced/telemetry). + ## Diagrams ### State diagram diff --git a/docs/strategies/fallback.md b/docs/strategies/fallback.md index 61b7eb18196..c77bd0ad173 100644 --- a/docs/strategies/fallback.md +++ b/docs/strategies/fallback.md @@ -2,12 +2,19 @@ ## About -- **Options**: [`FallbackStrategyOptions`](xref:Polly.Fallback.FallbackStrategyOptions`1) -- **Extensions**: `AddFallback` -- **Strategy Type**: Reactive +- **Option(s)**: + - [`FallbackStrategyOptions`](xref:Polly.Fallback.FallbackStrategyOptions`1) +- **Extension(s)**: + - `AddFallback` +- **Exception(s)**: - --- +The fallback **reactive** resilience strategy provides a substitute if the execution of the callback fails. Failure can be either an `Exception` or a result object indicating unsuccessful processing. Typically this strategy is used as a last resort, meaning that if all other strategies failed to overcome the transient failure you could still provide a fallback value to the caller. + +> [!NOTE] +> In this document the *fallback*, *substitute*, and *surrogate* terms are used interchangeably. + ## Usage @@ -59,11 +66,40 @@ new ResiliencePipelineBuilder().AddFallback(optionsOnFallback); ## 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. | +| Property | Default Value | Description | +|------------------|---------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------| +| `ShouldHandle` | Any exceptions other than `OperationCanceledException`. | Defines a predicate to determine what results and/or exceptions are handled by the fallback strategy. | +| `FallbackAction` | `Null`, **Required** | This delegate allows you to **dynamically** calculate the surrogate value by utilizing information that is only available at runtime (like the outcome). | +| `OnFallback` | `null` | If provided then it will be invoked before the strategy calculates the fallback value. | + +## Telemetry + +The fallback strategy reports the following telemetry events: + +| Event Name | Event Severity | When? | +|--------------|----------------|----------------------------------------------------------| +| `OnFallback` | `Warning` | Just before the strategy calls the `OnFallback` delegate | + +Here are some sample events: + +```none +Resilience event occurred. EventName: 'OnFallback', Source: 'MyPipeline/MyPipelineInstance/MyFallbackStrategy', Operation Key: 'MyFallbackGuardedOperation', Result: '-1' + +Resilience event occurred. EventName: 'OnFallback', Source: '(null)/(null)/Fallback', Operation Key: '', Result: 'Exception of type 'CustomException' was thrown.' + CustomException: Exception of type 'CustomException' was thrown. + at Program.<>c.
b__0_3(ResilienceContext ctx) + ... + at Polly.ResiliencePipeline.<>c__8`1.<b__8_0>d.MoveNext() in /_/src/Polly.Core/ResiliencePipeline.AsyncT.cs:line 95 +``` + +> [!NOTE] +> Please note that the `OnFallback` telemetry event will be reported **only if** the fallback strategy provides a surrogate value. +> +> So, if the callback either returns an acceptable result or throws an unhandled exception then there will be no telemetry emitted. +> +> Also remember that the `Result` will be **always populated** for the `OnFallback` telemetry event. + +For further information please check out the [telemetry page](https://www.pollydocs.org/advanced/telemetry). ## Diagrams @@ -298,7 +334,7 @@ return await fallback.ExecuteAsync(CallPrimary, CancellationToken.None); ### Nesting `ExecuteAsync` calls -Combining multiple strategies can be achieved in various ways. However, deeply nesting `ExecuteAsync` calls can lead to what's commonly referred to as _`Execute` Hell_. +Combining multiple strategies can be achieved in various ways. However, deeply nesting `ExecuteAsync` calls can lead to what's commonly referred to as *`Execute` Hell*. > [!NOTE] > While this isn't strictly tied to the Fallback mechanism, it's frequently observed when Fallback is the outermost layer. @@ -323,7 +359,7 @@ return result; **Reasoning**: -This is akin to JavaScript's [callback hell](http://callbackhell.com/) or _[the pyramid of doom](https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming))_. It's easy to mistakenly reference the wrong `CancellationToken` parameter. +This is akin to JavaScript's [callback hell](http://callbackhell.com/) or *[the pyramid of doom](https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming))*. It's easy to mistakenly reference the wrong `CancellationToken` parameter. ✅ DO diff --git a/docs/strategies/hedging.md b/docs/strategies/hedging.md index 515ca0dabf1..c5af808899f 100644 --- a/docs/strategies/hedging.md +++ b/docs/strategies/hedging.md @@ -2,15 +2,17 @@ ## About -- **Options**: [`HedgingStrategyOptions`](xref:Polly.Hedging.HedgingStrategyOptions`1) -- **Extensions**: `AddHedging` -- **Strategy Type**: Reactive +- **Option(s)**: + - [`HedgingStrategyOptions`](xref:Polly.Hedging.HedgingStrategyOptions`1) +- **Extension(s)**: + - `AddHedging` +- **Exception(s)**: - --- -The hedging strategy enables the re-execution of a user-defined callback if the previous execution takes too long. This approach gives you the option to either run the original callback again or specify a new callback for subsequent hedged attempts. Implementing a hedging strategy can boost the overall responsiveness of the system. However, it's essential to note that this improvement comes at the cost of increased resource utilization. If low latency is not a critical requirement, you may find the [retry strategy](retry.md) is more appropriate. +The hedging **reactive** strategy enables the re-execution of the callback if the previous execution takes too long. This approach gives you the option to either run the original callback again or specify a new callback for subsequent *hedged* attempts. Implementing a hedging strategy can boost the overall responsiveness of the system. However, it's essential to note that this improvement comes at the cost of increased resource utilization. If low latency is not a critical requirement, you may find the [retry strategy](retry.md) more appropriate. -This strategy also supports multiple [concurrency modes](#concurrency-modes) for added flexibility. +This strategy also supports multiple [concurrency modes](#concurrency-modes) to flexibly tailor the behavior for your own needs. > [!NOTE] > Please do not start any background work when executing actions using the hedging strategy. This strategy can spawn multiple parallel tasks, and as a result multiple background tasks can be started. @@ -59,14 +61,14 @@ new ResiliencePipelineBuilder().AddHedging(optionsDefaults) ## 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. | +| Property | Default Value | Description | +|---------------------|--------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `ShouldHandle` | Any exceptions other than `OperationCanceledException`. | Defines a predicate to determine what results and/or exceptions are handled by the hedging 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` | It returns the original callback that was passed to this strategy. | This delegate allows you to **dynamically** calculate the hedged action by utilizing information that is only available at runtime (like the attempt number). | +| `DelayGenerator` | `null` | This optional delegate allows you to **dynamically** calculate the delay by utilizing information that is only available at runtime (like the attempt number). | +| `OnHedging` | `null` | If provided then it will be invoked before the strategy performs the hedged action. | You can use the following special values for `Delay` or in `DelayGenerator`: @@ -76,6 +78,43 @@ You can use the following special values for `Delay` or in `DelayGenerator`: > [!NOTE] > If both `Delay` and `DelayGenerator` are specified then `Delay` will be ignored. +## Telemetry + +The hedging strategy reports the following telemetry events: + +| Event Name | Event Severity | When? | +|--------------------|---------------------------|----------------------------------------------------------------------| +| `ExecutionAttempt` | `Information` / `Warning` | Just after the original/hedged action completes with success/failure | +| `OnHedging` | `Warning` | Just before the strategy calls the `OnHedging` delegate | + +Here are some sample events: + +The reported `Execution Attempt` telemetry events' severity depends on the action's outcome: + +- If it succeeded then the severity is `Information` +- It it failed then the severity is `Warning` + +```none +Resilience event occurred. EventName: 'OnHedging', Source: 'MyPipeline/MyPipelineInstance/Hedging', Operation Key: 'MyHedgingOperation', Result: '' + +Execution attempt. Source: 'MyPipeline/MyPipelineInstance/Hedging', Operation Key: 'MyHedgingOperation', Result: '1', Handled: 'False', Attempt: '0', Execution Time: '1505.3839' + +Execution attempt. Source: 'MyPipeline/MyPipelineInstance/Hedging', Operation Key: 'MyHedgingOperation', Result: 'Exception of type 'CustomException' was thrown.', Handled: 'True', Attempt: '1', Execution Time: '1525.2899' + CustomException: Exception of type 'CustomException' was thrown. + at Program.<>c.<
b__0_2>d.MoveNext() + ... + at Polly.ResiliencePipeline.<>c__8`1.<b__8_0>d.MoveNext() in /_/src/Polly.Core/ResiliencePipeline.AsyncT.cs:line 95 +``` + +> [!NOTE] +> Please note that the `OnHedging` telemetry event will be reported **only if** the hedging strategy performs any hedged actions. +> +> On the other hand the `Execution attempt` event will be **always** reported regardless whether the strategy has to perform hedging. +> +> Also remember that `Attempt: '0'` relates to the original execution attempt. + +For further information please check out the [telemetry page](https://www.pollydocs.org/advanced/telemetry). + ## Concurrency modes In the sections below, explore the different concurrency modes available in the hedging strategy. The behavior is primarily controlled by the `Delay` property value. diff --git a/docs/strategies/rate-limiter.md b/docs/strategies/rate-limiter.md index f268f8234cb..9d720c970e1 100644 --- a/docs/strategies/rate-limiter.md +++ b/docs/strategies/rate-limiter.md @@ -2,16 +2,20 @@ ## About -- **Options**: [`RateLimiterStrategyOptions`](xref:Polly.RateLimiting.RateLimiterStrategyOptions) -- **Extensions**: `AddRateLimiter`, `AddConcurrencyLimiter` -- **Strategy Type**: Proactive -- **Exceptions**: +- **Option(s)**: + - [`RateLimiterStrategyOptions`](xref:Polly.RateLimiting.RateLimiterStrategyOptions) +- **Extension(s)**: + - `AddRateLimiter`, + - `AddConcurrencyLimiter` +- **Exception(s)**: - `RateLimiterRejectedException`: Thrown when a rate limiter rejects an execution. -- **Package**: [Polly.RateLimiting](https://www.nuget.org/packages/Polly.RateLimiting) + +> [!NOTE] +> The rate limiter strategy resides inside the [Polly.RateLimiting](https://www.nuget.org/packages/Polly.RateLimiting) package, not in ([Polly.Core](https://www.nuget.org/packages/Polly.Core)) like other strategies. --- -The rate limiter resilience strategy controls the number of operations that can pass through it. This strategy is a thin layer over the API provided by the [`System.Threading.RateLimiting`](https://www.nuget.org/packages/System.Threading.RateLimiting) package. +The rate limiter **proactive** resilience strategy controls the number of operations that can pass through it. This strategy is a thin layer over the API provided by the [`System.Threading.RateLimiting`](https://www.nuget.org/packages/System.Threading.RateLimiting) package. This strategy can be used in two flavors: to control inbound load via a rate limiter and to control outbound load via a concurrency limiter. Further reading: @@ -107,6 +111,16 @@ catch (RateLimiterRejectedException) ``` +## Defaults + +| Property | Default Value | Description | +|-----------------------------|------------------------------------------------------|-------------------------------------------------------------------------------------------------------------| +| `RateLimiter` | `null` | **Dynamically** creates a `RateLimitLease` for executions. | +| `DefaultRateLimiterOptions` | `PermitLimit` set to 1000 and `QueueLimit` set to 0. | If `RateLimiter` is not provided then this options object will be used for the default concurrency limiter. | +| `OnRejected` | `null` | If provided then it will be invoked after the limiter rejected an execution. | + +### `OnRejected` versus catching `RateLimiterRejectedException` + The `OnRejected` user-provided delegate is called just before the strategy throws the `RateLimiterRejectedException`. This delegate receives a parameter which allows you to access the `Context` object as well as the `Lease`: - Accessing the `Context` is also possible via a different `Execute{Async}` overload. @@ -116,13 +130,31 @@ So, what is the purpose of the `OnRejected`? The `OnRejected` delegate can be useful when you define a resilience pipeline which consists of multiple strategies. For example, you have a rate limiter as the inner strategy and a retry as the outer strategy. If the retry is defined to handle `RateLimiterRejectedException`, that means the `Execute{Async}` may or may not throw that exception depending on future attempts. So, if you want to get notification about the fact that the rate limit has been exceeded, you have to provide a delegate to the `OnRejected` property. -## Defaults +> [!IMPORTANT] +> The [`RateLimiterRejectedException`](xref:Polly.RateLimiting.RateLimiterRejectedException) has a `RetryAfter` property. If this optional `TimeSpan` is provided then this indicates that your requests are throttled and you should retry them no sooner than the value given. +> Please note that this information is not available inside the `OnRejected` callback. + +## Telemetry + +The rate limiter strategy reports the following telemetry events: + +| Event Name | Event Severity | When? | +|-------------------------|----------------|----------------------------------------------------------| +| `OnRateLimiterRejected` | `Error` | Just before the strategy calls the `OnRejected` delegate | + +Here are some sample events: + +```none +Resilience event occurred. EventName: 'OnRateLimiterRejected', Source: '(null)/(null)/RateLimiter', Operation Key: '', Result: '' +Resilience event occurred. EventName: 'OnRateLimiterRejected', Source: 'MyPipeline/MyPipelineInstance/MyRateLimiterStrategy', Operation Key: 'MyRateLimitedOperation', Result: '' +``` + +> [!NOTE] +> Please note that the `OnRateLimiterRejected` telemetry event will be reported **only if** the rate limiter strategy rejects the provided callback execution. +> +> Also remember that the `Result` will be **always empty** for the `OnRateLimiterRejected` telemetry event. -| 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. | +For further information please check out the [telemetry page](https://www.pollydocs.org/advanced/telemetry). ## Diagrams diff --git a/docs/strategies/retry.md b/docs/strategies/retry.md index 5c3be0a6d63..7cfddc9e5b7 100644 --- a/docs/strategies/retry.md +++ b/docs/strategies/retry.md @@ -2,14 +2,17 @@ ## About -- **Options**: +- **Option(s)**: - [`RetryStrategyOptions`](xref:Polly.Retry.RetryStrategyOptions) - [`RetryStrategyOptions`](xref:Polly.Retry.RetryStrategyOptions`1) -- **Extensions**: `AddRetry` -- **Strategy Type**: Reactive +- **Extension(s)**: + - `AddRetry` +- **Exception(s)**: - --- +The retry **reactive** resilience strategy re-executes the same callback method if its execution fails. Failure can be either an `Exception` or a result object indicating unsuccessful processing. Between the retry attempts the retry strategy waits a specified amount of time. You have fine-grained control over how to calculate the next delay. The retry strategy stops invoking the same callback when it reaches the maximum allowed number of retry attempts or an unhandled exception is thrown / result object indicating a failure is returned. + ## Usage @@ -97,16 +100,75 @@ new ResiliencePipelineBuilder().AddRetry(optionsExtractDela ## 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. | -| `MaxDelay` | `null` | Caps the calculated retry delay to a specified maximum duration. | +| Property | Default Value | Description | +|--------------------|---------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `ShouldHandle` | Any exceptions other than `OperationCanceledException`. | Defines a predicate to determine what results and/or exceptions are handled by the retry strategy. | +| `MaxRetryAttempts` | 3 | The maximum number of retry attempts to use, in addition to the original call. | +| `BackoffType` | Constant | The back-off algorithm type to generate the delay(s) between retry attempts. | +| `Delay` | 2 seconds | The *base* delay between retry attempts. See the next section for more details. | +| `MaxDelay` | `null` | If provided then the strategy caps the calculated retry delay to this value. | +| `UseJitter` | False | If set to `true`, a jitter (random value) is added to retry delays. See the next section for more details. | +| `DelayGenerator` | `null` | This optional delegate allows you to **dynamically** calculate the retry delay by utilizing information that is only available at runtime (like the attempt number). | +| `OnRetry` | `null` | If provided then it will be invoked before the strategy delays the next attempt. | + +## Telemetry + +The retry strategy reports the following telemetry events: + +| Event Name | Event Severity | When? | +|--------------------|---------------------------|-------------------------------------------------------| +| `ExecutionAttempt` | `Information` / `Warning` | Just before the strategy calculates the next delay | +| `OnRetry` | `Warning` | Just before the strategy calls the `OnRetry` delegate | + +Here are some sample events: + +### Unhandled case + +If the retry strategy does not perform any retries then the reported telemetry events' severity will be `Information`: + +```none +Execution attempt. Source: 'MyPipeline/MyPipelineInstance/MyRetryStrategy', Operation Key: 'MyRetryableOperation', Result: '1', Handled: 'False', Attempt: '0', Execution Time: '110.952' + +Execution attempt. Source: 'MyPipeline/MyPipelineInstance/MyRetryStrategy', Operation Key: 'MyRetryableOperation', Result: 'Failed', Handled: 'False', Attempt: '0', Execution Time: '5.2194' + System.Exception: Failed + at Program.<>c.
b__0_1(ResilienceContext ctx) + ... + at Polly.ResiliencePipeline.<>c.<b__1_0>d.MoveNext() in /_/src/Polly.Core/ResiliencePipeline.Async.cs:line 67 +``` + +### Handled case + +If the retry strategy performs some retries then the reported telemetry events' severity will be `Warning`: + +```none +Execution attempt. Source: 'MyPipeline/MyPipelineInstance/MyRetryStrategy', Operation Key: 'MyRetryableOperation', Result: 'Failed', Handled: 'True', Attempt: '0', Execution Time: '5.0397' + System.Exception: Failed + at Program.<>c.
b__0_1(ResilienceContext ctx) + ... + at Polly.ResiliencePipeline.<>c.<b__1_0>d.MoveNext() in /_/src/Polly.Core/ResiliencePipeline.Async.cs:line 67 + +Resilience event occurred. EventName: 'OnRetry', Source: 'MyPipeline/MyPipelineInstance/MyRetryStrategy', Operation Key: 'MyRetryableOperation', Result: 'Failed' + System.Exception: Failed + at Program.<>c.
b__0_1(ResilienceContext ctx) + ... + at Polly.ResiliencePipeline.<>c.<b__1_0>d.MoveNext() in /_/src/Polly.Core/ResiliencePipeline.Async.cs:line 67 + + +Execution attempt. Source: 'MyPipeline/MyPipelineInstance/MyRetryStrategy', Operation Key: 'MyRetryableOperation', Result: 'Failed', Handled: 'True', Attempt: '1', Execution Time: '0.1159' + System.Exception: Failed + at Program.<>c.
b__0_1(ResilienceContext ctx) + ... + at Polly.ResiliencePipeline.<>c.<b__1_0>d.MoveNext() in /_/src/Polly.Core/ResiliencePipeline.Async.cs:line 67 +``` + +> [!NOTE] +> Please note that the `OnRetry` telemetry event will be reported **only if** the retry strategy performs any retry attempts. +> +> On the other hand the `Execution attempt` event will be **always** reported regardless whether the strategy has to perform any retries. +> +> Also remember that `Attempt: '0'` relates to the original execution attempt. + +For further information please check out the [telemetry page](https://www.pollydocs.org/advanced/telemetry). ## Calculation of the next delay diff --git a/docs/strategies/timeout.md b/docs/strategies/timeout.md index 66753fa71a2..a39f49c2a6a 100644 --- a/docs/strategies/timeout.md +++ b/docs/strategies/timeout.md @@ -2,15 +2,16 @@ ## About -- **Options**: [`TimeoutStrategyOptions`](xref:Polly.Timeout.TimeoutStrategyOptions) -- **Extensions**: `AddTimeout` -- **Strategy Type**: Proactive -- **Exceptions**: +- **Option(s)**: + - [`TimeoutStrategyOptions`](xref:Polly.Timeout.TimeoutStrategyOptions) +- **Extension(s)**: + - `AddTimeout` +- **Exception(s)**: - `TimeoutRejectedException`: Thrown when a delegate executed through a timeout strategy does not complete before the timeout. --- -The timeout resilience strategy cancels the execution if it does not complete within the specified timeout period. If the execution is canceled by the timeout strategy, it throws a `TimeoutRejectedException`. The timeout strategy operates by wrapping the incoming cancellation token with a new one. Should the original token be canceled, the timeout strategy will transparently honor the original cancellation token without throwing a `TimeoutRejectedException`. +The timeout **proactive** resilience strategy cancels the execution if it does not complete within the specified timeout period. If the execution is canceled by the timeout strategy, it throws a `TimeoutRejectedException`. The timeout strategy operates by wrapping the incoming cancellation token with a new one. Should the original token be canceled, the timeout strategy will transparently honor the original cancellation token without throwing a `TimeoutRejectedException`. > [!IMPORTANT] > It is crucial that the user's callback respects the cancellation token. If it does not, the callback will continue executing even after a cancellation request, thereby ignoring the cancellation. @@ -108,6 +109,19 @@ catch (TimeoutRejectedException) ``` +## Defaults + +| Property | Default Value | Description | +|--------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------| +| `Timeout` | 30 seconds | Defines a **static** period within which the delegate should complete, otherwise it will be cancelled. | +| `TimeoutGenerator` | `null` | This delegate allows you to **dynamically** calculate the timeout period by utilizing information that is only available at runtime. | +| `OnTimeout` | `null` | If provided then it will be invoked after the timeout occurred. | + +> [!NOTE] +> If both `Timeout` and `TimeoutGenerator` are specified then `Timeout` will be ignored. + +### `OnTimeout` versus catching `TimeoutRejectedException` + The `OnTimeout` user-provided delegate is called just before the strategy throws the `TimeoutRejectedException`. This delegate receives a parameter which allows you to access the `Context` object as well as the `Timeout`: - Accessing the `Context` is also possible via a different `Execute{Async}` overload. @@ -117,16 +131,29 @@ So, what is the purpose of the `OnTimeout` in case of static timeout settings? The `OnTimeout` delegate can be useful when you define a resilience pipeline which consists of multiple strategies. For example you have a timeout as the inner strategy and a retry as the outer strategy. If the retry is defined to handle `TimeoutRejectedException`, that means the `Execute{Async}` may or may not throw that exception depending on future attempts. So, if you want to get notification about the fact that a timeout has occurred, you have to provide a delegate to the `OnTimeout` property. -## Defaults +## Telemetry + +The timeout strategy reports the following telemetry events: -| 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. | +| Event Name | Event Severity | When? | +|-------------|----------------|---------------------------------------------------------| +| `OnTimeout` | `Error` | Just before the strategy calls the `OnTimeout` delegate | + +Here are some sample events: + +```none +Resilience event occurred. EventName: 'OnTimeout', Source: '(null)/(null)/Timeout', Operation Key: '', Result: '' +Resilience event occurred. EventName: 'OnTimeout', Source: 'MyPipeline/MyPipelineInstance/MyTimeoutStrategy', Operation Key: 'MyTimeoutGuardedOperation', Result: '' +``` > [!NOTE] -> If both `Timeout` and `TimeoutGenerator` are specified then `Timeout` will be ignored. +> Please note that the `OnTimeout` telemetry event will be reported **only if** the timeout strategy cancels the provided callback execution. +> +> So, if the callback either finishes on time or throws an exception then there will be no telemetry emitted. +> +> Also remember that the `Result` will be **always empty** for the `OnTimeout` telemetry event. + +For further information please check out the [telemetry page](https://www.pollydocs.org/advanced/telemetry). ## Diagrams diff --git a/src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs b/src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs index 70645725cd1..e8b37c22401 100644 --- a/src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs +++ b/src/Polly.Core/CircuitBreaker/Controller/CircuitStateController.cs @@ -333,8 +333,6 @@ private void OpenCircuitFor_NeedsLock(Outcome outcome, TimeSpan breakDuration } _blockedUntil = IsDateTimeOverflow(utcNow, breakDuration) ? DateTimeOffset.MaxValue : utcNow + breakDuration; - - var transitionedState = _circuitState; _circuitState = CircuitState.Open; var args = new OnCircuitOpenedArguments(context, outcome, breakDuration, manual);