Use new chaos APIs to simplify the usage examples (#1912)

This commit is contained in:
martintmk 2024-01-22 10:33:02 +01:00 коммит произвёл GitHub
Родитель cf008353cd
Коммит 2e1a2a56e8
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
11 изменённых файлов: 254 добавлений и 50 удалений

Просмотреть файл

@ -459,6 +459,10 @@ Rate limiter strategy throws `RateLimiterRejectedException` if execution is reje
For more details, visit the [rate limiter strategy](https://www.pollydocs.org/strategies/rate-limiter) documentation.
## Chaos engineering
Starting with version `8.3.0`, Polly has integrated [Simmy](https://github.com/Polly-Contrib/Simmy), a chaos engineering library, directly into its core. For more information, please refer to the dedicated [chaos engineering documentation](https://www.pollydocs.org/chaos/).
## Next steps
To learn more about Polly, visit [pollydocs.org][polly-docs].

Просмотреть файл

@ -17,7 +17,7 @@ The behavior chaos strategy is designed to inject custom behaviors into system o
<!-- snippet: chaos-behavior-usage -->
```cs
// To use a custom function to generate the behavior to inject.
// To use a custom delegated for injected behavior
var optionsWithBehaviorGenerator = new BehaviorStrategyOptions
{
BehaviorAction = static args => RestartRedisVM(),
@ -42,7 +42,7 @@ var optionsOnBehaviorInjected = new BehaviorStrategyOptions
new ResiliencePipelineBuilder().AddChaosBehavior(optionsWithBehaviorGenerator);
new ResiliencePipelineBuilder<HttpResponseMessage>().AddChaosBehavior(optionsOnBehaviorInjected);
// There are also a handy overload to inject the chaos easily.
// There are also a handy overload to inject the chaos easily
new ResiliencePipelineBuilder().AddChaosBehavior(0.05, RestartRedisVM);
```
<!-- endSnippet -->

Просмотреть файл

@ -17,15 +17,17 @@ The fault chaos strategy is designed to introduce faults (exceptions) into the s
<!-- snippet: chaos-fault-usage -->
```cs
// 10% of invocations will be randomly affected.
// 10% of invocations will be randomly affected and one of the exceptions will be thrown (equal probability).
var optionsBasic = new FaultStrategyOptions
{
FaultGenerator = static args => new ValueTask<Exception?>(new InvalidOperationException("Dummy exception")),
FaultGenerator = new FaultGenerator()
.AddException<InvalidOperationException>() // Uses default constructor
.AddException(() => new TimeoutException("Chaos timeout injected.")), // Custom exception generator
Enabled = true,
InjectionRate = 0.1
};
// To use a custom function to generate the fault to inject.
// To use a custom delegate to generate the fault to be injected
var optionsWithFaultGenerator = new FaultStrategyOptions
{
FaultGenerator = static args =>
@ -34,7 +36,9 @@ var optionsWithFaultGenerator = new FaultStrategyOptions
{
"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
// When the fault generator returns null, the strategy won't inject
// any fault and just invokes the user's callback.
_ => null
};
return new ValueTask<Exception?>(exception);
@ -46,7 +50,7 @@ var optionsWithFaultGenerator = new FaultStrategyOptions
// To get notifications when a fault is injected
var optionsOnFaultInjected = new FaultStrategyOptions
{
FaultGenerator = static args => new ValueTask<Exception?>(new InvalidOperationException("Dummy exception")),
FaultGenerator = new FaultGenerator().AddException<InvalidOperationException>(),
Enabled = true,
InjectionRate = 0.1,
OnFaultInjected = static args =>
@ -60,7 +64,7 @@ var optionsOnFaultInjected = new FaultStrategyOptions
new ResiliencePipelineBuilder().AddChaosFault(optionsBasic);
new ResiliencePipelineBuilder<HttpResponseMessage>().AddChaosFault(optionsWithFaultGenerator);
// There are also a couple of handy overloads to inject the chaos easily.
// There are also a couple of handy overloads to inject the chaos easily
new ResiliencePipelineBuilder().AddChaosFault(0.1, () => new InvalidOperationException("Dummy exception"));
```
<!-- endSnippet -->
@ -136,3 +140,53 @@ sequenceDiagram
F->>P: Throws injected Fault
P->>C: Propagates Exception
```
## Generating faults
To generate a fault, you need to specify a `FaultGenerator` delegate. You have the following options as to how you customize this delegate:
### Use `FaultGenerator` class to generate faults
The `FaultGenerator` is convenience API that allows you to specify what faults (exceptions) are to be injected. Additionally, it also allows assigning weight to each registered fault.
<!-- snippet: chaos-fault-generator-class -->
```cs
new ResiliencePipelineBuilder()
.AddChaosFault(new FaultStrategyOptions
{
// Use FaultGenerator to register exceptions to be injected
FaultGenerator = new FaultGenerator()
.AddException<InvalidOperationException>() // Uses default constructor
.AddException(() => new TimeoutException("Chaos timeout injected.")) // Custom exception generator
.AddException(context => CreateExceptionFromContext(context)) // Access the ResilienceContext
.AddException<TimeoutException>(weight: 50), // Assign weight to the exception, default is 100
});
```
<!-- endSnippet -->
### Use delegates to generate faults
Delegates give you the most flexibility at the expense of slightly more complicated syntax. Delegates also support asynchronous fault generation, if you ever need that possibility.
<!-- snippet: chaos-fault-generator-delegate -->
```cs
new ResiliencePipelineBuilder()
.AddChaosFault(new FaultStrategyOptions
{
// The same behavior can be achieved with delegates
FaultGenerator = args =>
{
Exception? exception = Random.Shared.Next(350) switch
{
< 100 => new InvalidOperationException(),
< 200 => new TimeoutException("Chaos timeout injected."),
< 300 => CreateExceptionFromContext(args.Context),
< 350 => new TimeoutException(),
_ => null
};
return new ValueTask<Exception?>(exception);
}
});
```
<!-- endSnippet -->

Просмотреть файл

@ -48,7 +48,7 @@ All the strategies' options implement the [`MonkeyStrategyOptions`](xref:Polly.S
| 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. |
| `InjectionRate` | 0.001 | 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. |

Просмотреть файл

@ -21,7 +21,7 @@ The latency chaos strategy is designed to introduce controlled delays into syste
// See https://www.pollydocs.org/chaos/latency#defaults for defaults.
var optionsDefault = new LatencyStrategyOptions();
// 10% of invocations will be randomly affected.
// 10% of invocations will be randomly affected
var basicOptions = new LatencyStrategyOptions
{
Latency = TimeSpan.FromSeconds(30),
@ -29,7 +29,7 @@ var basicOptions = new LatencyStrategyOptions
InjectionRate = 0.1
};
// To use a custom function to generate the latency to inject.
// To use a custom function to generate the latency to inject
var optionsWithLatencyGenerator = new LatencyStrategyOptions
{
LatencyGenerator = static args =>
@ -38,7 +38,9 @@ var optionsWithLatencyGenerator = new LatencyStrategyOptions
{
"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
// When the latency generator returns Zero, the strategy
// won't inject any delay and just invokes the user's callback.
_ => TimeSpan.Zero
};
return new ValueTask<TimeSpan>(latency);
@ -64,7 +66,7 @@ var optionsOnBehaviorInjected = new LatencyStrategyOptions
new ResiliencePipelineBuilder().AddChaosLatency(optionsDefault);
new ResiliencePipelineBuilder<HttpStatusCode>().AddChaosLatency(optionsWithLatencyGenerator);
// There are also a handy overload to inject the chaos easily.
// There are also a handy overload to inject the chaos easily
new ResiliencePipelineBuilder().AddChaosLatency(0.1, TimeSpan.FromSeconds(30));
```
<!-- endSnippet -->

Просмотреть файл

@ -19,14 +19,13 @@ The outcome chaos strategy is designed to inject or substitute fake results into
<!-- snippet: chaos-result-usage -->
```cs
// To use a custom function to generate the result to inject.
// To use OutcomeGenerator<T> to register the results and exceptions to be injected (equal probability)
var optionsWithResultGenerator = new OutcomeStrategyOptions<HttpResponseMessage>
{
OutcomeGenerator = static args =>
{
var response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
return new ValueTask<Outcome<HttpResponseMessage>?>(Outcome.FromResult(response));
},
OutcomeGenerator = new OutcomeGenerator<HttpResponseMessage>()
.AddResult(() => new HttpResponseMessage(HttpStatusCode.TooManyRequests))
.AddResult(() => new HttpResponseMessage(HttpStatusCode.InternalServerError))
.AddException(() => new HttpRequestException("Chaos request exception.")),
Enabled = true,
InjectionRate = 0.1
};
@ -34,11 +33,8 @@ var optionsWithResultGenerator = new OutcomeStrategyOptions<HttpResponseMessage>
// To get notifications when a result is injected
var optionsOnBehaviorInjected = new OutcomeStrategyOptions<HttpResponseMessage>
{
OutcomeGenerator = static args =>
{
var response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
return new ValueTask<Outcome<HttpResponseMessage>?>(Outcome.FromResult(response));
},
OutcomeGenerator = new OutcomeGenerator<HttpResponseMessage>()
.AddResult(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)),
Enabled = true,
InjectionRate = 0.1,
OnOutcomeInjected = static args =>
@ -52,7 +48,7 @@ var optionsOnBehaviorInjected = new OutcomeStrategyOptions<HttpResponseMessage>
new ResiliencePipelineBuilder<HttpResponseMessage>().AddChaosResult(optionsWithResultGenerator);
new ResiliencePipelineBuilder<HttpResponseMessage>().AddChaosResult(optionsOnBehaviorInjected);
// There are also a couple of handy overloads to inject the chaos easily.
// There are also a couple of handy overloads to inject the chaos easily
new ResiliencePipelineBuilder<HttpResponseMessage>().AddChaosResult(0.1, () => new HttpResponseMessage(HttpStatusCode.TooManyRequests));
```
<!-- endSnippet -->
@ -136,3 +132,53 @@ sequenceDiagram
B->>P: Returns result
P->>C: Returns result
```
## Generating outcomes
To generate a faulted outcome (result or exception), you need to specify a `OutcomeGenerator` delegate. You have the following options as to how you customize this delegate:
### Use `OutcomeGenerator<T>` class to generate outcomes
The `OutcomeGenerator<T>` is a convenience API that allows you to specify what outcomes (results or exceptions) are to be injected. Additionally, it also allows assigning weight to each registered outcome.
<!-- snippet: chaos-outcome-generator-class -->
```cs
new ResiliencePipelineBuilder<HttpResponseMessage>()
.AddChaosResult(new OutcomeStrategyOptions<HttpResponseMessage>
{
// Use OutcomeGenerator<T> to register the results and exceptions to be injected
OutcomeGenerator = new OutcomeGenerator<HttpResponseMessage>()
.AddResult(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)) // Result generator
.AddResult(() => new HttpResponseMessage(HttpStatusCode.TooManyRequests), weight: 50) // Result generator with weight
.AddResult(context => CreateResultFromContext(context)) // Access the ResilienceContext to create result
.AddException<HttpRequestException>(), // You can also register exceptions
});
```
<!-- endSnippet -->
### Use delegates to generate faults
Delegates give you the most flexibility at the expense of slightly more complicated syntax. Delegates also support asynchronous outcome generation, if you ever need that possibility.
<!-- snippet: chaos-outcome-generator-delegate -->
```cs
new ResiliencePipelineBuilder<HttpResponseMessage>()
.AddChaosResult(new OutcomeStrategyOptions<HttpResponseMessage>
{
// The same behavior can be achieved with delegates
OutcomeGenerator = args =>
{
Outcome<HttpResponseMessage>? outcome = Random.Shared.Next(350) switch
{
< 100 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)),
< 150 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.TooManyRequests)),
< 250 => Outcome.FromResult(CreateResultFromContext(args.Context)),
< 350 => Outcome.FromException<HttpResponseMessage>(new TimeoutException()),
_ => null
};
return ValueTask.FromResult(outcome);
}
});
```
<!-- endSnippet -->

Просмотреть файл

@ -47,7 +47,7 @@
- name: Advanced topics
expanded: true
items:
- name: Telemetry and monitoring
- name: Telemetry
href: advanced/telemetry.md
- name: Dependency injection
href: advanced/dependency-injection.md

Просмотреть файл

@ -12,7 +12,7 @@ internal static partial class Chaos
static ValueTask RestartRedisVM() => ValueTask.CompletedTask;
#region chaos-behavior-usage
// To use a custom function to generate the behavior to inject.
// To use a custom delegated for injected behavior
var optionsWithBehaviorGenerator = new BehaviorStrategyOptions
{
BehaviorAction = static args => RestartRedisVM(),
@ -37,7 +37,7 @@ internal static partial class Chaos
new ResiliencePipelineBuilder().AddChaosBehavior(optionsWithBehaviorGenerator);
new ResiliencePipelineBuilder<HttpResponseMessage>().AddChaosBehavior(optionsOnBehaviorInjected);
// There are also a handy overload to inject the chaos easily.
// There are also a handy overload to inject the chaos easily
new ResiliencePipelineBuilder().AddChaosBehavior(0.05, RestartRedisVM);
#endregion

Просмотреть файл

@ -5,20 +5,24 @@ using Polly.Simmy.Fault;
namespace Snippets.Docs;
#pragma warning disable CA5394 // Do not use insecure randomness
internal static partial class Chaos
{
public static void FaultUsage()
{
#region chaos-fault-usage
// 10% of invocations will be randomly affected.
// 10% of invocations will be randomly affected and one of the exceptions will be thrown (equal probability).
var optionsBasic = new FaultStrategyOptions
{
FaultGenerator = static args => new ValueTask<Exception?>(new InvalidOperationException("Dummy exception")),
FaultGenerator = new FaultGenerator()
.AddException<InvalidOperationException>() // Uses default constructor
.AddException(() => new TimeoutException("Chaos timeout injected.")), // Custom exception generator
Enabled = true,
InjectionRate = 0.1
};
// To use a custom function to generate the fault to inject.
// To use a custom delegate to generate the fault to be injected
var optionsWithFaultGenerator = new FaultStrategyOptions
{
FaultGenerator = static args =>
@ -27,7 +31,9 @@ internal static partial class Chaos
{
"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
// When the fault generator returns null, the strategy won't inject
// any fault and just invokes the user's callback.
_ => null
};
return new ValueTask<Exception?>(exception);
@ -39,7 +45,7 @@ internal static partial class Chaos
// To get notifications when a fault is injected
var optionsOnFaultInjected = new FaultStrategyOptions
{
FaultGenerator = static args => new ValueTask<Exception?>(new InvalidOperationException("Dummy exception")),
FaultGenerator = new FaultGenerator().AddException<InvalidOperationException>(),
Enabled = true,
InjectionRate = 0.1,
OnFaultInjected = static args =>
@ -53,7 +59,7 @@ internal static partial class Chaos
new ResiliencePipelineBuilder().AddChaosFault(optionsBasic);
new ResiliencePipelineBuilder<HttpResponseMessage>().AddChaosFault(optionsWithFaultGenerator);
// There are also a couple of handy overloads to inject the chaos easily.
// There are also a couple of handy overloads to inject the chaos easily
new ResiliencePipelineBuilder().AddChaosFault(0.1, () => new InvalidOperationException("Dummy exception"));
#endregion
@ -76,4 +82,50 @@ internal static partial class Chaos
.Build();
#endregion
}
public static void FaultGenerator()
{
#region chaos-fault-generator-class
new ResiliencePipelineBuilder()
.AddChaosFault(new FaultStrategyOptions
{
// Use FaultGenerator to register exceptions to be injected
FaultGenerator = new FaultGenerator()
.AddException<InvalidOperationException>() // Uses default constructor
.AddException(() => new TimeoutException("Chaos timeout injected.")) // Custom exception generator
.AddException(context => CreateExceptionFromContext(context)) // Access the ResilienceContext
.AddException<TimeoutException>(weight: 50), // Assign weight to the exception, default is 100
});
#endregion
}
public static void FaultGeneratorDelegates()
{
#region chaos-fault-generator-delegate
new ResiliencePipelineBuilder()
.AddChaosFault(new FaultStrategyOptions
{
// The same behavior can be achieved with delegates
FaultGenerator = args =>
{
Exception? exception = Random.Shared.Next(350) switch
{
< 100 => new InvalidOperationException(),
< 200 => new TimeoutException("Chaos timeout injected."),
< 300 => CreateExceptionFromContext(args.Context),
< 350 => new TimeoutException(),
_ => null
};
return new ValueTask<Exception?>(exception);
}
});
#endregion
}
private static Exception CreateExceptionFromContext(ResilienceContext context) => new InvalidOperationException();
}

Просмотреть файл

@ -15,7 +15,7 @@ internal static partial class Chaos
// See https://www.pollydocs.org/chaos/latency#defaults for defaults.
var optionsDefault = new LatencyStrategyOptions();
// 10% of invocations will be randomly affected.
// 10% of invocations will be randomly affected
var basicOptions = new LatencyStrategyOptions
{
Latency = TimeSpan.FromSeconds(30),
@ -23,7 +23,7 @@ internal static partial class Chaos
InjectionRate = 0.1
};
// To use a custom function to generate the latency to inject.
// To use a custom function to generate the latency to inject
var optionsWithLatencyGenerator = new LatencyStrategyOptions
{
LatencyGenerator = static args =>
@ -32,7 +32,9 @@ internal static partial class Chaos
{
"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
// When the latency generator returns Zero, the strategy
// won't inject any delay and just invokes the user's callback.
_ => TimeSpan.Zero
};
return new ValueTask<TimeSpan>(latency);
@ -58,7 +60,7 @@ internal static partial class Chaos
new ResiliencePipelineBuilder().AddChaosLatency(optionsDefault);
new ResiliencePipelineBuilder<HttpStatusCode>().AddChaosLatency(optionsWithLatencyGenerator);
// There are also a handy overload to inject the chaos easily.
// There are also a handy overload to inject the chaos easily
new ResiliencePipelineBuilder().AddChaosLatency(0.1, TimeSpan.FromSeconds(30));
#endregion

Просмотреть файл

@ -6,19 +6,20 @@ using Polly.Simmy.Outcomes;
namespace Snippets.Docs;
#pragma warning disable CA5394 // Do not use insecure randomness
internal static partial class Chaos
{
public static void ResultUsage()
{
#region chaos-result-usage
// To use a custom function to generate the result to inject.
// To use OutcomeGenerator<T> to register the results and exceptions to be injected (equal probability)
var optionsWithResultGenerator = new OutcomeStrategyOptions<HttpResponseMessage>
{
OutcomeGenerator = static args =>
{
var response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
return new ValueTask<Outcome<HttpResponseMessage>?>(Outcome.FromResult(response));
},
OutcomeGenerator = new OutcomeGenerator<HttpResponseMessage>()
.AddResult(() => new HttpResponseMessage(HttpStatusCode.TooManyRequests))
.AddResult(() => new HttpResponseMessage(HttpStatusCode.InternalServerError))
.AddException(() => new HttpRequestException("Chaos request exception.")),
Enabled = true,
InjectionRate = 0.1
};
@ -26,11 +27,8 @@ internal static partial class Chaos
// To get notifications when a result is injected
var optionsOnBehaviorInjected = new OutcomeStrategyOptions<HttpResponseMessage>
{
OutcomeGenerator = static args =>
{
var response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
return new ValueTask<Outcome<HttpResponseMessage>?>(Outcome.FromResult(response));
},
OutcomeGenerator = new OutcomeGenerator<HttpResponseMessage>()
.AddResult(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)),
Enabled = true,
InjectionRate = 0.1,
OnOutcomeInjected = static args =>
@ -44,7 +42,7 @@ internal static partial class Chaos
new ResiliencePipelineBuilder<HttpResponseMessage>().AddChaosResult(optionsWithResultGenerator);
new ResiliencePipelineBuilder<HttpResponseMessage>().AddChaosResult(optionsOnBehaviorInjected);
// There are also a couple of handy overloads to inject the chaos easily.
// There are also a couple of handy overloads to inject the chaos easily
new ResiliencePipelineBuilder<HttpResponseMessage>().AddChaosResult(0.1, () => new HttpResponseMessage(HttpStatusCode.TooManyRequests));
#endregion
@ -75,4 +73,50 @@ internal static partial class Chaos
.Build();
#endregion
}
public static void OutcomeGenerator()
{
#region chaos-outcome-generator-class
new ResiliencePipelineBuilder<HttpResponseMessage>()
.AddChaosResult(new OutcomeStrategyOptions<HttpResponseMessage>
{
// Use OutcomeGenerator<T> to register the results and exceptions to be injected
OutcomeGenerator = new OutcomeGenerator<HttpResponseMessage>()
.AddResult(() => new HttpResponseMessage(HttpStatusCode.InternalServerError)) // Result generator
.AddResult(() => new HttpResponseMessage(HttpStatusCode.TooManyRequests), weight: 50) // Result generator with weight
.AddResult(context => CreateResultFromContext(context)) // Access the ResilienceContext to create result
.AddException<HttpRequestException>(), // You can also register exceptions
});
#endregion
}
public static void OutcomeGeneratorDelegates()
{
#region chaos-outcome-generator-delegate
new ResiliencePipelineBuilder<HttpResponseMessage>()
.AddChaosResult(new OutcomeStrategyOptions<HttpResponseMessage>
{
// The same behavior can be achieved with delegates
OutcomeGenerator = args =>
{
Outcome<HttpResponseMessage>? outcome = Random.Shared.Next(350) switch
{
< 100 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)),
< 150 => Outcome.FromResult(new HttpResponseMessage(HttpStatusCode.TooManyRequests)),
< 250 => Outcome.FromResult(CreateResultFromContext(args.Context)),
< 350 => Outcome.FromException<HttpResponseMessage>(new TimeoutException()),
_ => null
};
return ValueTask.FromResult(outcome);
}
});
#endregion
}
private static HttpResponseMessage CreateResultFromContext(ResilienceContext context) => new(HttpStatusCode.TooManyRequests);
}