diff --git a/.github/wordlist.txt b/.github/wordlist.txt index 3c447baa572..6968808c5ec 100644 --- a/.github/wordlist.txt +++ b/.github/wordlist.txt @@ -32,6 +32,7 @@ nuget oss pcl parallelize +paas pluralsight pollydocs pre @@ -47,6 +48,7 @@ rethrow rethrows retryable reusability +saas sdk serializers silverlight @@ -60,6 +62,7 @@ telemetrylistener testability timingpolicy ui +unhandled uwp waitandretry wpf diff --git a/docs/advanced/simmy.md b/docs/advanced/simmy.md deleted file mode 100644 index 4bdbc2ff8b0..00000000000 --- a/docs/advanced/simmy.md +++ /dev/null @@ -1,6 +0,0 @@ -# Chaos engineering with Simmy - -[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] repository to find out more. - -[simmy]: https://github.com/Polly-Contrib/Simmy diff --git a/docs/chaos/behavior.md b/docs/chaos/behavior.md new file mode 100644 index 00000000000..777de831800 --- /dev/null +++ b/docs/chaos/behavior.md @@ -0,0 +1,130 @@ +# Behavior chaos strategy + +## About + +- **Options**: [`BehaviorStrategyOptions`](xref:Polly.Simmy.Behavior.BehaviorStrategyOptions) +- **Extensions**: `AddChaosBehavior` +- **Strategy Type**: Proactive + +--- + +The behavior chaos strategy is designed to inject custom behaviors into system operations right before such an operation is invoked. This strategy is flexible, allowing users to define specific behaviors such as altering the input, simulating resource exhaustion, putting the system in a given state before the actual operation is called, or other operational variations to simulate real-world scenarios. + +## Usage + + +```cs +// To use a custom function to generate the behavior to inject. +var optionsWithBehaviorGenerator = new BehaviorStrategyOptions +{ + BehaviorAction = static args => RestartRedisVM(), + Enabled = true, + InjectionRate = 0.05 +}; + +// To get notifications when a behavior is injected +var optionsOnBehaviorInjected = new BehaviorStrategyOptions +{ + BehaviorAction = static args => RestartRedisVM(), + Enabled = true, + InjectionRate = 0.05, + OnBehaviorInjected = static args => + { + Console.WriteLine("OnBehaviorInjected, Operation: {0}.", args.Context.OperationKey); + return default; + } +}; + +// Add a behavior strategy with a BehaviorStrategyOptions instance to the pipeline +new ResiliencePipelineBuilder().AddChaosBehavior(optionsWithBehaviorGenerator); +new ResiliencePipelineBuilder().AddChaosBehavior(optionsOnBehaviorInjected); + +// There are also a handy overload to inject the chaos easily. +new ResiliencePipelineBuilder().AddChaosBehavior(0.05, RestartRedisVM); +``` + + +Example execution: + + +```cs +var pipeline = 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), + }) + .AddChaosBehavior(new BehaviorStrategyOptions // Chaos strategies are usually placed as the last ones in the pipeline + { + BehaviorAction = static args => RestartRedisVM(), + Enabled = true, + InjectionRate = 0.05 + }) + .Build(); +``` + + +## Defaults + +| Property | Default Value | Description | +|----------------------|---------------|------------------------------------------------| +| `OnBehaviorInjected` | `null` | Action executed when the behavior is injected. | +| `BehaviorAction` | `null` | Custom behavior to be injected. | + +## Diagrams + +### Normal 🐵 sequence diagram + +```mermaid +sequenceDiagram + actor C as Caller + participant P as Pipeline + participant B as Behavior + participant D as DecoratedUserCallback + + C->>P: Calls ExecuteAsync + P->>B: Calls ExecuteCore + activate B + B-->>B: Determines Injection
Decision: 🐵 + deactivate B + B->>+D: Invokes + D->>-B: Returns result + B->>P: Returns result + P->>C: Returns result +``` + +### Chaos 🙈 sequence diagram + +```mermaid +sequenceDiagram + actor C as Caller + participant P as Pipeline + participant B as Behavior + participant D as DecoratedUserCallback + + C->>P: Calls ExecuteAsync + P->>B: Calls ExecuteCore + activate B + B-->>B: Determines Injection
Decision: 🙈 + B-->>B: Injects Behavior + deactivate B + B->>+D: Invokes + D->>-B: Returns result + B->>P: Returns result + P->>C: Returns result +``` + +## Anti-patterns + +### Injecting delay + +❌ DON'T + +Use behavior strategies to inject delays. + +✅ DO + +Use the latency chaos instead as the [`LatencyChaosStrategy`](latency.md) already correctly handles synchronous/asynchronous delay executions, cancellations, etc. diff --git a/docs/chaos/fault.md b/docs/chaos/fault.md new file mode 100644 index 00000000000..8f4c2deb3cf --- /dev/null +++ b/docs/chaos/fault.md @@ -0,0 +1,135 @@ +# Fault chaos strategy + +## About + +- **Options**: [`FaultStrategyOptions`](xref:Polly.Simmy.Fault.FaultStrategyOptions) +- **Extensions**: `AddChaosFault` +- **Strategy Type**: Proactive + +--- + +The fault chaos strategy is designed to introduce faults (exceptions) into the system, simulating real-world scenarios where operations might fail unexpectedly. It is configurable to inject specific types of exceptions or use custom logic to generate faults dynamically. + +## Usage + + +```cs +// 10% of invocations will be randomly affected. +var optionsBasic = new FaultStrategyOptions +{ + FaultGenerator = static args => new ValueTask(new InvalidOperationException("Dummy exception")), + Enabled = true, + InjectionRate = 0.1 +}; + +// To use a custom function to generate the fault to inject. +var optionsWithFaultGenerator = new FaultStrategyOptions +{ + FaultGenerator = static args => + { + Exception? exception = args.Context.OperationKey switch + { + "DataLayer" => new TimeoutException(), + "ApplicationLayer" => new InvalidOperationException(), + _ => null // When the fault generator returns null the strategy won't inject any fault and it will just invoke the user's callback + }; + + return new ValueTask(exception); + }, + Enabled = true, + InjectionRate = 0.1 +}; + +// To get notifications when a fault is injected +var optionsOnFaultInjected = new FaultStrategyOptions +{ + FaultGenerator = static args => new ValueTask(new InvalidOperationException("Dummy exception")), + Enabled = true, + InjectionRate = 0.1, + OnFaultInjected = static args => + { + Console.WriteLine("OnFaultInjected, Exception: {0}, Operation: {1}.", args.Fault.Message, args.Context.OperationKey); + return default; + } +}; + +// Add a fault strategy with a FaultStrategyOptions instance to the pipeline +new ResiliencePipelineBuilder().AddChaosFault(optionsBasic); +new ResiliencePipelineBuilder().AddChaosFault(optionsWithFaultGenerator); + +// There are also a couple of handy overloads to inject the chaos easily. +new ResiliencePipelineBuilder().AddChaosFault(0.1, () => new InvalidOperationException("Dummy exception")); +``` + + +Example execution: + + +```cs +var pipeline = 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), + }) + .AddChaosFault(new FaultStrategyOptions // Chaos strategies are usually placed as the last ones in the pipeline + { + FaultGenerator = static args => new ValueTask(new InvalidOperationException("Dummy exception")), + Enabled = true, + InjectionRate = 0.1 + }) + .Build(); +``` + + +## Defaults + +| Property | Default Value | Description | +|-------------------|---------------|------------------------------------------------------| +| `OnFaultInjected` | `null` | Action executed when the fault is injected. | +| `FaultGenerator` | `null` | Generates the fault to inject for a given execution. | + +## Diagrams + +### Normal 🐵 sequence diagram + +```mermaid +sequenceDiagram + actor C as Caller + participant P as Pipeline + participant F as Fault + participant D as DecoratedUserCallback + + C->>P: Calls ExecuteAsync + P->>F: Calls ExecuteCore + activate F + F-->>F: Determines Injection
Decision: 🐵 + deactivate F + F->>+D: Invokes + D->>-F: Returns result + F->>P: Returns result + P->>C: Returns result +``` + +### Chaos 🙈 sequence diagram + +```mermaid +sequenceDiagram + actor C as Caller + participant P as Pipeline + participant F as Fault + participant D as DecoratedUserCallback + + C->>P: Calls ExecuteAsync + P->>F: Calls ExecuteCore + activate F + F-->>F: Determines Injection
Decision: 🙈 + F-->>F: Injects Fault + deactivate F + Note over D: The user's Callback is not invoked
when a fault is injected + F->>P: Throws injected Fault + P->>C: Propagates Exception +``` diff --git a/docs/chaos/index.md b/docs/chaos/index.md new file mode 100644 index 00000000000..d9be339cd1f --- /dev/null +++ b/docs/chaos/index.md @@ -0,0 +1,53 @@ +# Chaos engineering with Simmy + +[Simmy][simmy] is a major new addition to Polly library, adding a chaos engineering and fault-injection dimension to Polly, through the provision of strategies to selectively inject faults, latency, custom behavior or fake results. + +![Simmy](../media/simmy-logo.png) + +## Motivation + +There are a lot of questions when it comes to chaos engineering and making sure that a system is actually ready to face the worst possible scenarios: + +* Is my system resilient enough? +* Am I handling the right exceptions/scenarios? +* How will my system behave if X happens? +* How can I test without waiting for a handled (or even unhandled) exception to happen in my production environment? + +Using Polly helps introduce resilience to a project, but we don't want to have to wait for expected or unexpected failures to test it out. A resilience could be wrongly implemented; testing the scenarios is not straightforward; and mocking failure of some dependencies (for example a cloud SaaS or PaaS service) is not always straightforward. + +### What is needed to simulate chaotic scenarios? + +* A way to simulate failures of dependencies (any service dependency for example). +* Define when to fail based on some external factors - maybe global configuration or some rule. +* A way to revert easily, to control the blast radius. +* To be production grade, to run this in a production or near-production system with automation. + +## Chaos strategies (a.k.a Monkey strategies) + +Chaos strategies (or Monkey strategies as we call them) are in essence a [Resilience strategy](../strategies/index.md#built-in-strategies), which means, as a *Resilience Strategy* is the minimum unit of resilience for Polly, a *Chaos Strategy* is the minimum unit of chaos for Simmy. + +### Built-in strategies + +| Strategy | Reactive | What does the strategy do? | +|-------------------------|----------|----------------------------------------------------------------------| +| [Fault](fault.md) | No | Injects exceptions in your system. | +| [Result](result.md) | Yes | Substitute results to fake outcomes in your system. | +| [Latency](latency.md) | No | Injects latency into executions before the calls are made. | +| [Behavior](behavior.md) | No | Allows you to inject *any* extra behaviour, before a call is placed. | + +## Usage + +It is usual to place the chaos strategy as the last strategy in the resilience pipeline. By placing the chaos strategies as last, they subvert the usual outbound call at the last minute, substituting their fault or adding extra latency, etc. The existing resilience strategies - further out in the `ResiliencePipeline` - still apply, so you can test how the Polly resilience strategies you have configured handle the chaos/faults injected by Simmy. + +## Common options across strategies + +All the strategies' options implement the [`MonkeyStrategyOptions`](xref:Polly.Simmy.MonkeyStrategyOptions) class as it contains the basic configuration for every chaos strategy. + +| Property | Default Value | Description | +|--------------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `InjectionRate` | 0.001 ms | A decimal between 0 and 1 inclusive. The strategy will inject the chaos, randomly, that proportion of the time, e.g.: if 0.2, twenty percent of calls will be randomly affected; if 0.01, one percent of calls; if 1, all calls. | +| `InjectionRateGenerator` | `null` | Generates the injection rate for a given execution, which the value should be between [0, 1] (inclusive). | +| `Enabled` | `false` | Determines whether the strategy is enabled or not. | +| `EnabledGenerator` | `null` | The generator that indicates whether the chaos strategy is enabled for a given execution. | + +[simmy]: https://github.com/Polly-Contrib/Simmy diff --git a/docs/chaos/latency.md b/docs/chaos/latency.md new file mode 100644 index 00000000000..8503acd1c4f --- /dev/null +++ b/docs/chaos/latency.md @@ -0,0 +1,142 @@ +# Latency chaos strategy + +## About + +- **Options**: [`LatencyStrategyOptions`](xref:Polly.Simmy.Latency.LatencyStrategyOptions) +- **Extensions**: `AddChaosLatency` +- **Strategy Type**: Proactive + +--- + +The latency chaos strategy is designed to introduce controlled delays into system operations, simulating network latency or slow processing times. This strategy helps in assessing and improving the resilience of applications against increased response times. + +## Usage + + +```cs +// Latency using the default options. +// See https://www.pollydocs.org/chaos/latency#defaults for defaults. +var optionsDefault = new LatencyStrategyOptions(); + +// 10% of invocations will be randomly affected. +var basicOptions = new LatencyStrategyOptions +{ + Latency = TimeSpan.FromSeconds(30), + Enabled = true, + InjectionRate = 0.1 +}; + +// To use a custom function to generate the latency to inject. +var optionsWithLatencyGenerator = new LatencyStrategyOptions +{ + LatencyGenerator = static args => + { + TimeSpan latency = args.Context.OperationKey switch + { + "DataLayer" => TimeSpan.FromMilliseconds(500), + "ApplicationLayer" => TimeSpan.FromSeconds(2), + _ => TimeSpan.Zero // When the latency generator returns Zero the strategy won't inject any delay and it will just invoke the user's callback + }; + + return new ValueTask(latency); + }, + Enabled = true, + InjectionRate = 0.1 +}; + +// To get notifications when a delay is injected +var optionsOnBehaviorInjected = new LatencyStrategyOptions +{ + Latency = TimeSpan.FromSeconds(30), + Enabled = true, + InjectionRate = 0.1, + OnLatency = static args => + { + Console.WriteLine($"OnLatency, Latency: {args.Latency}, Operation: {args.Context.OperationKey}."); + return default; + } +}; + +// Add a latency strategy with a LatencyStrategyOptions instance to the pipeline +new ResiliencePipelineBuilder().AddChaosLatency(optionsDefault); +new ResiliencePipelineBuilder().AddChaosLatency(optionsWithLatencyGenerator); + +// There are also a handy overload to inject the chaos easily. +new ResiliencePipelineBuilder().AddChaosLatency(0.1, TimeSpan.FromSeconds(30)); +``` + + +Example execution: + + +```cs +var pipeline = 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), + }) + .AddTimeout(TimeSpan.FromSeconds(5)) + .AddChaosLatency(new LatencyStrategyOptions // Chaos strategies are usually placed as the last ones in the pipeline + { + Latency = TimeSpan.FromSeconds(10), + Enabled = true, + InjectionRate = 0.1 + }) + .Build(); +``` + + +## Defaults + +| Property | Default Value | Description | +|--------------------|---------------|--------------------------------------------------------| +| `Latency` | `30 seconds` | A `TimeSpan` indicating the delay to be injected. | +| `LatencyGenerator` | `null` | Generates the latency to inject for a given execution. | +| `OnLatency` | `null` | Action executed when latency is injected. | + +## Diagrams + +### Normal 🐵 sequence diagram + +```mermaid +sequenceDiagram + actor C as Caller + participant P as Pipeline + participant L as Latency + participant D as DecoratedUserCallback + + C->>P: Calls ExecuteAsync + P->>L: Calls ExecuteCore + activate L + L->>L: Determines Injection
Decision: 🐵 + deactivate L + L->>+D: Invokes + D->>-L: Returns result + L->>P: Returns result + P->>C: Returns result +``` + +### Chaos 🙈 sequence diagram + +```mermaid +sequenceDiagram + actor C as Caller + participant P as Pipeline + participant L as Latency + participant D as DecoratedUserCallback + + C->>P: Calls ExecuteAsync + P->>L: Calls ExecuteCore + activate L + L->>L: Determines Injection
Decision: 🙈 + L-->>L: Injects Latency + deactivate L + L->>+D: Invokes + D->>-L: Returns result + L->>P: Returns result + P->>C: Returns result +``` diff --git a/docs/chaos/result.md b/docs/chaos/result.md new file mode 100644 index 00000000000..0ec40f41cbe --- /dev/null +++ b/docs/chaos/result.md @@ -0,0 +1,135 @@ +# Outcome chaos strategy + +## About + +- **Options**: + - [`OutcomeStrategyOptions`](xref:Polly.Simmy.Outcomes.OutcomeStrategyOptions) + - [`OutcomeStrategyOptions`](xref:Polly.Simmy.Outcomes.OutcomeStrategyOptions`1) +- **Extensions**: `AddChaosResult` +- **Strategy Type**: Reactive + +--- + +The outcome chaos strategy is designed to inject or substitute fake results into system operations. This allows testing how an application behaves when it receives different types of responses, like successful results, errors, or exceptions. + +## Usage + + +```cs +// To use a custom function to generate the result to inject. +var optionsWithResultGenerator = new OutcomeStrategyOptions +{ + OutcomeGenerator = static args => + { + var response = new HttpResponseMessage(HttpStatusCode.InternalServerError); + return new ValueTask?>(Outcome.FromResult(response)); + }, + Enabled = true, + InjectionRate = 0.1 +}; + +// To get notifications when a result is injected +var optionsOnBehaviorInjected = new OutcomeStrategyOptions +{ + OutcomeGenerator = static args => + { + var response = new HttpResponseMessage(HttpStatusCode.InternalServerError); + return new ValueTask?>(Outcome.FromResult(response)); + }, + Enabled = true, + InjectionRate = 0.1, + OnOutcomeInjected = static args => + { + Console.WriteLine($"OnBehaviorInjected, Outcome: {args.Outcome.Result}, Operation: {args.Context.OperationKey}."); + return default; + } +}; + +// Add a result strategy with a OutcomeStrategyOptions{} instance to the pipeline +new ResiliencePipelineBuilder().AddChaosResult(optionsWithResultGenerator); +new ResiliencePipelineBuilder().AddChaosResult(optionsOnBehaviorInjected); + +// There are also a couple of handy overloads to inject the chaos easily. +new ResiliencePipelineBuilder().AddChaosResult(0.1, () => new HttpResponseMessage(HttpStatusCode.TooManyRequests)); +``` + + +Example execution: + + +```cs +var pipeline = new ResiliencePipelineBuilder() + .AddRetry(new RetryStrategyOptions + { + ShouldHandle = static args => args.Outcome switch + { + { Result.StatusCode: HttpStatusCode.InternalServerError } => PredicateResult.True(), + _ => PredicateResult.False() + }, + BackoffType = DelayBackoffType.Exponential, + UseJitter = true, + MaxRetryAttempts = 4, + Delay = TimeSpan.FromSeconds(3), + }) + .AddChaosResult(new OutcomeStrategyOptions // Chaos strategies are usually placed as the last ones in the pipeline + { + OutcomeGenerator = static args => + { + var response = new HttpResponseMessage(HttpStatusCode.InternalServerError); + return new ValueTask?>(Outcome.FromResult(response)); + }, + Enabled = true, + InjectionRate = 0.1 + }) + .Build(); +``` + + +## Defaults + +| Property | Default Value | Description | +|---------------------|---------------|---------------------------------------------------------| +| `OutcomeGenerator` | `null` | Function to generate the outcome for a given execution. | +| `OnOutcomeInjected` | `null` | Action executed when the outcome is injected. | + +## Diagrams + +### Normal 🐵 sequence diagram + +```mermaid +sequenceDiagram + actor C as Caller + participant P as Pipeline + participant B as Outcome + participant D as DecoratedUserCallback + + C->>P: Calls ExecuteAsync + P->>B: Calls ExecuteCore + activate B + B-->>B: Determines Injection
Decision: 🐵 + deactivate B + B->>+D: Invokes + D->>-B: Returns result + B->>P: Returns result + P->>C: Returns result +``` + +### Chaos 🙈 sequence diagram + +```mermaid +sequenceDiagram + actor C as Caller + participant P as Pipeline + participant B as Outcome + participant D as DecoratedUserCallback + + C->>P: Calls ExecuteAsync + P->>B: Calls ExecuteCore + activate B + B-->>B: Determines Injection
Decision: 🙈 + B-->>B: Injects Outcome + deactivate B + Note over D: The user's Callback is not invoked
when a fake result is injected + B->>P: Returns result + P->>C: Returns result +``` diff --git a/docs/docfx.json b/docs/docfx.json index c3e49fe2506..bcf4076eada 100644 --- a/docs/docfx.json +++ b/docs/docfx.json @@ -56,6 +56,7 @@ { "files": [ "media/*.pdf", + "media/*.png", "icon.png" ] } diff --git a/docs/index.md b/docs/index.md index 9e9c7a2af3d..b552fb55de3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -33,7 +33,7 @@ Polly has a rich documentation that covers various topics, such as: - [Dependency injection](advanced/dependency-injection.md): How to integrate Polly with dependency injection frameworks and containers. - [Performance](advanced/performance.md): Tips on optimizing and getting the best performance from Polly. - [Testing](advanced/testing.md): How to test the composition and configuration of resilience pipelines. -- [Chaos engineering](advanced/simmy.md): How to use Polly to inject faults and test the resilience of your system. +- [Chaos engineering](chaos/index.md): How to use Polly to inject faults and test the resilience of your system. - [Extensibility](extensibility/index.md): How to create and use custom strategies and extensions for Polly. You can also find many resources and community contributions, such as: diff --git a/docs/media/simmy-logo.png b/docs/media/simmy-logo.png new file mode 100644 index 00000000000..3f55fd7e5c9 Binary files /dev/null and b/docs/media/simmy-logo.png differ diff --git a/docs/strategies/circuit-breaker.md b/docs/strategies/circuit-breaker.md index c737cfac512..50798e3b451 100644 --- a/docs/strategies/circuit-breaker.md +++ b/docs/strategies/circuit-breaker.md @@ -4,7 +4,7 @@ - **Options**: - [`CircuitBreakerStrategyOptions`](xref:Polly.CircuitBreaker.CircuitBreakerStrategyOptions) - - [`CircuitBreakerStrategyOptions`](xref:Polly.CircuitBreaker.CircuitBreakerStrategyOptions) + - [`CircuitBreakerStrategyOptions`](xref:Polly.CircuitBreaker.CircuitBreakerStrategyOptions`1) - **Extensions**: `AddCircuitBreaker` - **Strategy Type**: Reactive - **Exceptions**: @@ -328,7 +328,7 @@ sequenceDiagram D->>-CB: Fails Note over CB: Moves to Open state CB->>+BDG: Calls Generator - BDG->>-CB: Returns calculated
duration + BDG->>-CB: Returns calculated
duration Note over CB: Break duration start CB->>P: Propagates failure P->>C: Propagates failure diff --git a/docs/toc.yml b/docs/toc.yml index fa36b828efc..7ecd8688a22 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -24,7 +24,6 @@ - name: Circuit breaker href: strategies/circuit-breaker.md - - name: Resilience pipelines href: pipelines/index.md expanded: true @@ -32,6 +31,19 @@ - name: Resilience pipeline registry href: pipelines/resilience-pipeline-registry.md +- name: Chaos engineering + href: chaos/index.md + expanded: true + items: + - name: Fault + href: chaos/fault.md + - name: Result + href: chaos/result.md + - name: Latency + href: chaos/latency.md + - name: Behavior + href: chaos/behavior.md + - name: Advanced topics expanded: true items: @@ -45,8 +57,6 @@ href: advanced/performance.md - name: Testing href: advanced/testing.md - - name: Chaos engineering - href: advanced/simmy.md - name: Extensibility href: extensibility/index.md diff --git a/src/Polly.Core/Simmy/Fault/FaultStrategyOptions.cs b/src/Polly.Core/Simmy/Fault/FaultStrategyOptions.cs index d32f2b2e4ae..deb8d61a602 100644 --- a/src/Polly.Core/Simmy/Fault/FaultStrategyOptions.cs +++ b/src/Polly.Core/Simmy/Fault/FaultStrategyOptions.cs @@ -16,7 +16,7 @@ public class FaultStrategyOptions : MonkeyStrategyOptions public Func? OnFaultInjected { get; set; } = default!; /// - /// Gets or sets the outcome generator to be injected for a given execution. + /// Gets or sets the fault generator to be injected for a given execution. /// /// /// Defaults to . Either or this property is required. diff --git a/src/Snippets/Docs/Chaos.Behavior.cs b/src/Snippets/Docs/Chaos.Behavior.cs new file mode 100644 index 00000000000..c9368463f31 --- /dev/null +++ b/src/Snippets/Docs/Chaos.Behavior.cs @@ -0,0 +1,80 @@ +using System.Net.Http; +using Polly.Retry; +using Polly.Simmy; +using Polly.Simmy.Behavior; + +namespace Snippets.Docs; + +internal static partial class Chaos +{ + public static void BehaviorUsage() + { + static ValueTask RestartRedisVM() => ValueTask.CompletedTask; + + #region chaos-behavior-usage + // To use a custom function to generate the behavior to inject. + var optionsWithBehaviorGenerator = new BehaviorStrategyOptions + { + BehaviorAction = static args => RestartRedisVM(), + Enabled = true, + InjectionRate = 0.05 + }; + + // To get notifications when a behavior is injected + var optionsOnBehaviorInjected = new BehaviorStrategyOptions + { + BehaviorAction = static args => RestartRedisVM(), + Enabled = true, + InjectionRate = 0.05, + OnBehaviorInjected = static args => + { + Console.WriteLine("OnBehaviorInjected, Operation: {0}.", args.Context.OperationKey); + return default; + } + }; + + // Add a behavior strategy with a BehaviorStrategyOptions instance to the pipeline + new ResiliencePipelineBuilder().AddChaosBehavior(optionsWithBehaviorGenerator); + new ResiliencePipelineBuilder().AddChaosBehavior(optionsOnBehaviorInjected); + + // There are also a handy overload to inject the chaos easily. + new ResiliencePipelineBuilder().AddChaosBehavior(0.05, RestartRedisVM); + #endregion + + #region chaos-behavior-execution + var pipeline = 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), + }) + .AddChaosBehavior(new BehaviorStrategyOptions // Chaos strategies are usually placed as the last ones in the pipeline + { + BehaviorAction = static args => RestartRedisVM(), + Enabled = true, + InjectionRate = 0.05 + }) + .Build(); + #endregion + } +} + +internal class RedisConnectionException : Exception +{ + public RedisConnectionException() + { + } + + public RedisConnectionException(string message) + : base(message) + { + } + + public RedisConnectionException(string message, Exception innerException) + : base(message, innerException) + { + } +} diff --git a/src/Snippets/Docs/Chaos.Fault.cs b/src/Snippets/Docs/Chaos.Fault.cs new file mode 100644 index 00000000000..61ae15ab3ea --- /dev/null +++ b/src/Snippets/Docs/Chaos.Fault.cs @@ -0,0 +1,79 @@ +using System.Net.Http; +using Polly.Retry; +using Polly.Simmy; +using Polly.Simmy.Fault; + +namespace Snippets.Docs; + +internal static partial class Chaos +{ + public static void FaultUsage() + { + #region chaos-fault-usage + // 10% of invocations will be randomly affected. + var optionsBasic = new FaultStrategyOptions + { + FaultGenerator = static args => new ValueTask(new InvalidOperationException("Dummy exception")), + Enabled = true, + InjectionRate = 0.1 + }; + + // To use a custom function to generate the fault to inject. + var optionsWithFaultGenerator = new FaultStrategyOptions + { + FaultGenerator = static args => + { + Exception? exception = args.Context.OperationKey switch + { + "DataLayer" => new TimeoutException(), + "ApplicationLayer" => new InvalidOperationException(), + _ => null // When the fault generator returns null the strategy won't inject any fault and it will just invoke the user's callback + }; + + return new ValueTask(exception); + }, + Enabled = true, + InjectionRate = 0.1 + }; + + // To get notifications when a fault is injected + var optionsOnFaultInjected = new FaultStrategyOptions + { + FaultGenerator = static args => new ValueTask(new InvalidOperationException("Dummy exception")), + Enabled = true, + InjectionRate = 0.1, + OnFaultInjected = static args => + { + Console.WriteLine("OnFaultInjected, Exception: {0}, Operation: {1}.", args.Fault.Message, args.Context.OperationKey); + return default; + } + }; + + // Add a fault strategy with a FaultStrategyOptions instance to the pipeline + new ResiliencePipelineBuilder().AddChaosFault(optionsBasic); + new ResiliencePipelineBuilder().AddChaosFault(optionsWithFaultGenerator); + + // There are also a couple of handy overloads to inject the chaos easily. + new ResiliencePipelineBuilder().AddChaosFault(0.1, () => new InvalidOperationException("Dummy exception")); + #endregion + + #region chaos-fault-execution + var pipeline = 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), + }) + .AddChaosFault(new FaultStrategyOptions // Chaos strategies are usually placed as the last ones in the pipeline + { + FaultGenerator = static args => new ValueTask(new InvalidOperationException("Dummy exception")), + Enabled = true, + InjectionRate = 0.1 + }) + .Build(); + #endregion + } +} diff --git a/src/Snippets/Docs/Chaos.Latency.cs b/src/Snippets/Docs/Chaos.Latency.cs new file mode 100644 index 00000000000..75fdf174d9e --- /dev/null +++ b/src/Snippets/Docs/Chaos.Latency.cs @@ -0,0 +1,85 @@ +using System.Net; +using Polly.Retry; +using Polly.Simmy; +using Polly.Simmy.Latency; +using Polly.Timeout; + +namespace Snippets.Docs; + +internal static partial class Chaos +{ + public static void LatencyUsage() + { + #region chaos-latency-usage + // Latency using the default options. + // See https://www.pollydocs.org/chaos/latency#defaults for defaults. + var optionsDefault = new LatencyStrategyOptions(); + + // 10% of invocations will be randomly affected. + var basicOptions = new LatencyStrategyOptions + { + Latency = TimeSpan.FromSeconds(30), + Enabled = true, + InjectionRate = 0.1 + }; + + // To use a custom function to generate the latency to inject. + var optionsWithLatencyGenerator = new LatencyStrategyOptions + { + LatencyGenerator = static args => + { + TimeSpan latency = args.Context.OperationKey switch + { + "DataLayer" => TimeSpan.FromMilliseconds(500), + "ApplicationLayer" => TimeSpan.FromSeconds(2), + _ => TimeSpan.Zero // When the latency generator returns Zero the strategy won't inject any delay and it will just invoke the user's callback + }; + + return new ValueTask(latency); + }, + Enabled = true, + InjectionRate = 0.1 + }; + + // To get notifications when a delay is injected + var optionsOnBehaviorInjected = new LatencyStrategyOptions + { + Latency = TimeSpan.FromSeconds(30), + Enabled = true, + InjectionRate = 0.1, + OnLatency = static args => + { + Console.WriteLine($"OnLatency, Latency: {args.Latency}, Operation: {args.Context.OperationKey}."); + return default; + } + }; + + // Add a latency strategy with a LatencyStrategyOptions instance to the pipeline + new ResiliencePipelineBuilder().AddChaosLatency(optionsDefault); + new ResiliencePipelineBuilder().AddChaosLatency(optionsWithLatencyGenerator); + + // There are also a handy overload to inject the chaos easily. + new ResiliencePipelineBuilder().AddChaosLatency(0.1, TimeSpan.FromSeconds(30)); + #endregion + + #region chaos-latency-execution + var pipeline = 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), + }) + .AddTimeout(TimeSpan.FromSeconds(5)) + .AddChaosLatency(new LatencyStrategyOptions // Chaos strategies are usually placed as the last ones in the pipeline + { + Latency = TimeSpan.FromSeconds(10), + Enabled = true, + InjectionRate = 0.1 + }) + .Build(); + #endregion + } +} diff --git a/src/Snippets/Docs/Chaos.Result.cs b/src/Snippets/Docs/Chaos.Result.cs new file mode 100644 index 00000000000..cd8161a5488 --- /dev/null +++ b/src/Snippets/Docs/Chaos.Result.cs @@ -0,0 +1,78 @@ +using System.Net; +using System.Net.Http; +using Polly.Retry; +using Polly.Simmy; +using Polly.Simmy.Outcomes; + +namespace Snippets.Docs; + +internal static partial class Chaos +{ + public static void ResultUsage() + { + #region chaos-result-usage + // To use a custom function to generate the result to inject. + var optionsWithResultGenerator = new OutcomeStrategyOptions + { + OutcomeGenerator = static args => + { + var response = new HttpResponseMessage(HttpStatusCode.InternalServerError); + return new ValueTask?>(Outcome.FromResult(response)); + }, + Enabled = true, + InjectionRate = 0.1 + }; + + // To get notifications when a result is injected + var optionsOnBehaviorInjected = new OutcomeStrategyOptions + { + OutcomeGenerator = static args => + { + var response = new HttpResponseMessage(HttpStatusCode.InternalServerError); + return new ValueTask?>(Outcome.FromResult(response)); + }, + Enabled = true, + InjectionRate = 0.1, + OnOutcomeInjected = static args => + { + Console.WriteLine($"OnBehaviorInjected, Outcome: {args.Outcome.Result}, Operation: {args.Context.OperationKey}."); + return default; + } + }; + + // Add a result strategy with a OutcomeStrategyOptions{} instance to the pipeline + new ResiliencePipelineBuilder().AddChaosResult(optionsWithResultGenerator); + new ResiliencePipelineBuilder().AddChaosResult(optionsOnBehaviorInjected); + + // There are also a couple of handy overloads to inject the chaos easily. + new ResiliencePipelineBuilder().AddChaosResult(0.1, () => new HttpResponseMessage(HttpStatusCode.TooManyRequests)); + #endregion + + #region chaos-result-execution + var pipeline = new ResiliencePipelineBuilder() + .AddRetry(new RetryStrategyOptions + { + ShouldHandle = static args => args.Outcome switch + { + { Result.StatusCode: HttpStatusCode.InternalServerError } => PredicateResult.True(), + _ => PredicateResult.False() + }, + BackoffType = DelayBackoffType.Exponential, + UseJitter = true, + MaxRetryAttempts = 4, + Delay = TimeSpan.FromSeconds(3), + }) + .AddChaosResult(new OutcomeStrategyOptions // Chaos strategies are usually placed as the last ones in the pipeline + { + OutcomeGenerator = static args => + { + var response = new HttpResponseMessage(HttpStatusCode.InternalServerError); + return new ValueTask?>(Outcome.FromResult(response)); + }, + Enabled = true, + InjectionRate = 0.1 + }) + .Build(); + #endregion + } +}