Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simmy API review Part 4 - Rename BehaviorAction to BehaviorGenerator #1917

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions docs/chaos/behavior.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ The behavior chaos strategy is designed to inject custom behaviors into system o
// To use a custom delegated for injected behavior
var optionsWithBehaviorGenerator = new ChaosBehaviorStrategyOptions
{
BehaviorAction = static args => RestartRedisAsync(args.Context.CancellationToken),
BehaviorGenerator = static args => RestartRedisAsync(args.Context.CancellationToken),
Enabled = true,
InjectionRate = 0.05
};

// To get notifications when a behavior is injected
var optionsOnBehaviorInjected = new ChaosBehaviorStrategyOptions
{
BehaviorAction = static args => RestartRedisAsync(args.Context.CancellationToken),
BehaviorGenerator = static args => RestartRedisAsync(args.Context.CancellationToken),
Enabled = true,
InjectionRate = 0.05,
OnBehaviorInjected = static args =>
Expand Down Expand Up @@ -62,7 +62,7 @@ var pipeline = new ResiliencePipelineBuilder()
})
.AddChaosBehavior(new ChaosBehaviorStrategyOptions // Chaos strategies are usually placed as the last ones in the pipeline
{
BehaviorAction = static args => RestartRedisAsync(args.Context.CancellationToken),
BehaviorGenerator = static args => RestartRedisAsync(args.Context.CancellationToken),
Enabled = true,
InjectionRate = 0.05
})
Expand All @@ -75,7 +75,7 @@ var pipeline = new ResiliencePipelineBuilder()
| Property | Default Value | Description |
|----------------------|---------------|------------------------------------------------|
| `OnBehaviorInjected` | `null` | Action executed when the behavior is injected. |
| `BehaviorAction` | `null` | Custom behavior to be injected. |
| `BehaviorGenerator` | `null` | Custom behavior to be injected. |

## Diagrams

Expand Down Expand Up @@ -128,6 +128,31 @@ sequenceDiagram

Use behavior strategies to inject delays.

<!-- snippet: chaos-behavior-anti-pattern-inject-delay -->
```cs
var pipeline = new ResiliencePipelineBuilder()
.AddChaosBehavior(new ChaosBehaviorStrategyOptions
{
BehaviorGenerator = static async args =>
{
await Task.Delay(TimeSpan.FromSeconds(7), args.Context.CancellationToken);
}
})
.Build();
```
<!-- endSnippet -->

✅ DO

Use the latency chaos instead as the [`ChaosLatencyStrategy`](latency.md) already correctly handles synchronous/asynchronous delay executions, cancellations, etc.

<!-- snippet: chaos-behavior-pattern-inject-delay -->
```cs
var pipeline = new ResiliencePipelineBuilder()
.AddChaosLatency(new ChaosLatencyStrategyOptions
{
Latency = TimeSpan.FromSeconds(7),
})
.Build();
```
<!-- endSnippet -->
12 changes: 6 additions & 6 deletions src/Polly.Core/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#nullable enable
Polly.CircuitBreaker.BreakDurationGeneratorArguments.BreakDurationGeneratorArguments(double failureRate, int failureCount, Polly.ResilienceContext! context, int halfOpenAttempts) -> void
Polly.CircuitBreaker.BreakDurationGeneratorArguments.HalfOpenAttempts.get -> int
Polly.Simmy.Behavior.BehaviorActionArguments
Polly.Simmy.Behavior.BehaviorActionArguments.BehaviorActionArguments() -> void
Polly.Simmy.Behavior.BehaviorActionArguments.BehaviorActionArguments(Polly.ResilienceContext! context) -> void
Polly.Simmy.Behavior.BehaviorActionArguments.Context.get -> Polly.ResilienceContext!
Polly.Simmy.Behavior.BehaviorGeneratorArguments
Polly.Simmy.Behavior.BehaviorGeneratorArguments.BehaviorGeneratorArguments() -> void
Polly.Simmy.Behavior.BehaviorGeneratorArguments.BehaviorGeneratorArguments(Polly.ResilienceContext! context) -> void
Polly.Simmy.Behavior.BehaviorGeneratorArguments.Context.get -> Polly.ResilienceContext!
Polly.Simmy.Behavior.ChaosBehaviorStrategyOptions
Polly.Simmy.Behavior.ChaosBehaviorStrategyOptions.BehaviorAction.get -> System.Func<Polly.Simmy.Behavior.BehaviorActionArguments, System.Threading.Tasks.ValueTask>?
Polly.Simmy.Behavior.ChaosBehaviorStrategyOptions.BehaviorAction.set -> void
Polly.Simmy.Behavior.ChaosBehaviorStrategyOptions.BehaviorGenerator.get -> System.Func<Polly.Simmy.Behavior.BehaviorGeneratorArguments, System.Threading.Tasks.ValueTask>?
Polly.Simmy.Behavior.ChaosBehaviorStrategyOptions.BehaviorGenerator.set -> void
Polly.Simmy.Behavior.ChaosBehaviorStrategyOptions.ChaosBehaviorStrategyOptions() -> void
Polly.Simmy.Behavior.ChaosBehaviorStrategyOptions.OnBehaviorInjected.get -> System.Func<Polly.Simmy.Behavior.OnBehaviorInjectedArguments, System.Threading.Tasks.ValueTask>?
Polly.Simmy.Behavior.ChaosBehaviorStrategyOptions.OnBehaviorInjected.set -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
/// <summary>
/// Arguments used by the behavior chaos strategy to execute a user's delegate custom action.
/// </summary>
public readonly struct BehaviorActionArguments
public readonly struct BehaviorGeneratorArguments
{
/// <summary>
/// Initializes a new instance of the <see cref="BehaviorActionArguments"/> struct.
/// Initializes a new instance of the <see cref="BehaviorGeneratorArguments"/> struct.
/// </summary>
/// <param name="context">The context associated with the execution of a user-provided callback.</param>
public BehaviorActionArguments(ResilienceContext context) => Context = context;
public BehaviorGeneratorArguments(ResilienceContext context) => Context = context;

/// <summary>
/// Gets the ResilienceContext instance.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public static TBuilder AddChaosBehavior<TBuilder>(this TBuilder builder, double
{
Enabled = true,
InjectionRate = injectionRate,
BehaviorAction = args => behavior(args.Context.CancellationToken)
BehaviorGenerator = args => behavior(args.Context.CancellationToken)
});
}

Expand Down
4 changes: 2 additions & 2 deletions src/Polly.Core/Simmy/Behavior/ChaosBehaviorStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ public ChaosBehaviorStrategy(
{
_telemetry = telemetry;
OnBehaviorInjected = options.OnBehaviorInjected;
Behavior = options.BehaviorAction!;
Behavior = options.BehaviorGenerator!;
}

public Func<OnBehaviorInjectedArguments, ValueTask>? OnBehaviorInjected { get; }

public Func<BehaviorActionArguments, ValueTask> Behavior { get; }
public Func<BehaviorGeneratorArguments, ValueTask> Behavior { get; }

protected internal override async ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ public class ChaosBehaviorStrategyOptions : ChaosStrategyOptions
/// Defaults to <see langword="null"/>.
/// </remarks>
[Required]
public Func<BehaviorActionArguments, ValueTask>? BehaviorAction { get; set; }
public Func<BehaviorGeneratorArguments, ValueTask>? BehaviorGenerator { get; set; }
}
35 changes: 32 additions & 3 deletions src/Snippets/Docs/Chaos.Behavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Polly.Retry;
using Polly.Simmy;
using Polly.Simmy.Behavior;
using Polly.Simmy.Latency;

namespace Snippets.Docs;

Expand All @@ -13,15 +14,15 @@ public static void BehaviorUsage()
// To use a custom delegated for injected behavior
var optionsWithBehaviorGenerator = new ChaosBehaviorStrategyOptions
{
BehaviorAction = static args => RestartRedisAsync(args.Context.CancellationToken),
BehaviorGenerator = static args => RestartRedisAsync(args.Context.CancellationToken),
Enabled = true,
InjectionRate = 0.05
};

// To get notifications when a behavior is injected
var optionsOnBehaviorInjected = new ChaosBehaviorStrategyOptions
{
BehaviorAction = static args => RestartRedisAsync(args.Context.CancellationToken),
BehaviorGenerator = static args => RestartRedisAsync(args.Context.CancellationToken),
Enabled = true,
InjectionRate = 0.05,
OnBehaviorInjected = static args =>
Expand Down Expand Up @@ -51,13 +52,41 @@ public static void BehaviorUsage()
})
.AddChaosBehavior(new ChaosBehaviorStrategyOptions // Chaos strategies are usually placed as the last ones in the pipeline
{
BehaviorAction = static args => RestartRedisAsync(args.Context.CancellationToken),
BehaviorGenerator = static args => RestartRedisAsync(args.Context.CancellationToken),
Enabled = true,
InjectionRate = 0.05
})
.Build();
#endregion
}

public static void AntiPattern_InjectDelay()
{
#region chaos-behavior-anti-pattern-inject-delay
var pipeline = new ResiliencePipelineBuilder()
.AddChaosBehavior(new ChaosBehaviorStrategyOptions
{
BehaviorGenerator = static async args =>
{
await Task.Delay(TimeSpan.FromSeconds(7), args.Context.CancellationToken);
}
})
.Build();

#endregion
}

public static void Pattern_InjectDelay()
{
#region chaos-behavior-pattern-inject-delay
var pipeline = new ResiliencePipelineBuilder()
.AddChaosLatency(new ChaosLatencyStrategyOptions
{
Latency = TimeSpan.FromSeconds(7),
})
.Build();
#endregion
}
}

internal class RedisConnectionException : Exception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

namespace Polly.Core.Tests.Simmy.Behavior;

public class BehaviorActionArgumentsTests
public class BehaviorGeneratorArgumentsTests
{
[Fact]
public void Ctor_Ok()
{
var args = new BehaviorActionArguments(ResilienceContextPool.Shared.Get());
var args = new BehaviorGeneratorArguments(ResilienceContextPool.Shared.Get());
args.Context.Should().NotBeNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void AddBehavior_Options_Ok()
{
Enabled = true,
InjectionRate = 1,
BehaviorAction = (_) => default
BehaviorGenerator = (_) => default
})
.Build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public void Ctor_Ok()
sut.EnabledGenerator.Should().BeNull();
sut.InjectionRate.Should().Be(ChaosStrategyConstants.DefaultInjectionRate);
sut.InjectionRateGenerator.Should().BeNull();
sut.BehaviorAction.Should().BeNull();
sut.BehaviorGenerator.Should().BeNull();
sut.OnBehaviorInjected.Should().BeNull();
}

Expand All @@ -33,7 +33,7 @@ public void InvalidOptions()
Invalid Options

Validation Errors:
The BehaviorAction field is required.
The BehaviorGenerator field is required.
""");
}
}
32 changes: 16 additions & 16 deletions test/Polly.Core.Tests/Simmy/Behavior/ChaosBehaviorStrategyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class ChaosBehaviorStrategyTests
private readonly ResilienceStrategyTelemetry _telemetry;
private readonly ChaosBehaviorStrategyOptions _options;
private readonly List<TelemetryEventArguments<object, object>> _args = [];
private bool _behaviorActionExecuted;
private bool _behaviorGeneratorExecuted;
private bool _onBehaviorInjectedExecuted;
private bool _userDelegateExecuted;

Expand All @@ -17,7 +17,7 @@ public ChaosBehaviorStrategyTests()
_telemetry = TestUtilities.CreateResilienceTelemetry(arg => _args.Add(arg));
_options = new();
_userDelegateExecuted = false;
_behaviorActionExecuted = false;
_behaviorGeneratorExecuted = false;
_onBehaviorInjectedExecuted = false;
}

Expand All @@ -27,13 +27,13 @@ public void Given_not_enabled_should_not_inject_behavior()
_options.InjectionRate = 0.6;
_options.Enabled = false;
_options.Randomizer = () => 0.5;
_options.BehaviorAction = (_) => { _behaviorActionExecuted = true; return default; };
_options.BehaviorGenerator = (_) => { _behaviorGeneratorExecuted = true; return default; };

var sut = CreateSut();
sut.Execute(() => { _userDelegateExecuted = true; });

_userDelegateExecuted.Should().BeTrue();
_behaviorActionExecuted.Should().BeFalse();
_behaviorGeneratorExecuted.Should().BeFalse();
}

[Fact]
Expand All @@ -42,13 +42,13 @@ public async Task Given_enabled_and_randomly_within_threshold_should_inject_beha
_options.InjectionRate = 0.6;
_options.Enabled = true;
_options.Randomizer = () => 0.5;
_options.BehaviorAction = (_) => { _behaviorActionExecuted = true; return default; };
_options.BehaviorGenerator = (_) => { _behaviorGeneratorExecuted = true; return default; };

var sut = CreateSut();
await sut.ExecuteAsync((_) => { _userDelegateExecuted = true; return default; });

_userDelegateExecuted.Should().BeTrue();
_behaviorActionExecuted.Should().BeTrue();
_behaviorGeneratorExecuted.Should().BeTrue();
_onBehaviorInjectedExecuted.Should().BeFalse();
}

Expand All @@ -58,7 +58,7 @@ public async Task Given_enabled_and_randomly_within_threshold_ensure_on_behavior
_options.InjectionRate = 0.6;
_options.Enabled = true;
_options.Randomizer = () => 0.5;
_options.BehaviorAction = (_) => { _behaviorActionExecuted = true; return default; };
_options.BehaviorGenerator = (_) => { _behaviorGeneratorExecuted = true; return default; };
_options.OnBehaviorInjected = args =>
{
args.Context.Should().NotBeNull();
Expand All @@ -72,7 +72,7 @@ public async Task Given_enabled_and_randomly_within_threshold_ensure_on_behavior

_onBehaviorInjectedExecuted.Should().BeTrue();
_userDelegateExecuted.Should().BeTrue();
_behaviorActionExecuted.Should().BeTrue();
_behaviorGeneratorExecuted.Should().BeTrue();
_args.Should().HaveCount(1);
_args[0].Arguments.Should().BeOfType<OnBehaviorInjectedArguments>();
}
Expand All @@ -83,13 +83,13 @@ public async Task Given_enabled_and_randomly_not_within_threshold_should_not_inj
_options.InjectionRate = 0.4;
_options.Enabled = false;
_options.Randomizer = () => 0.5;
_options.BehaviorAction = (_) => { _behaviorActionExecuted = true; return default; };
_options.BehaviorGenerator = (_) => { _behaviorGeneratorExecuted = true; return default; };

var sut = CreateSut();
await sut.ExecuteAsync((_) => { _userDelegateExecuted = true; return default; });

_userDelegateExecuted.Should().BeTrue();
_behaviorActionExecuted.Should().BeFalse();
_behaviorGeneratorExecuted.Should().BeFalse();
_onBehaviorInjectedExecuted.Should().BeFalse();
}

Expand All @@ -99,18 +99,18 @@ public async Task Should_inject_behavior_before_executing_user_delegate()
_options.InjectionRate = 0.6;
_options.Enabled = true;
_options.Randomizer = () => 0.5;
_options.BehaviorAction = (_) =>
_options.BehaviorGenerator = (_) =>
{
_userDelegateExecuted.Should().BeFalse(); // Not yet executed at the time the injected behavior runs.
_behaviorActionExecuted = true;
_behaviorGeneratorExecuted = true;
return default;
};

var sut = CreateSut();
await sut.ExecuteAsync((_) => { _userDelegateExecuted = true; return default; });

_userDelegateExecuted.Should().BeTrue();
_behaviorActionExecuted.Should().BeTrue();
_behaviorGeneratorExecuted.Should().BeTrue();
_onBehaviorInjectedExecuted.Should().BeFalse();
}

Expand All @@ -121,10 +121,10 @@ public async Task Should_not_execute_user_delegate_when_it_was_cancelled_running
_options.InjectionRate = 0.6;
_options.Enabled = true;
_options.Randomizer = () => 0.5;
_options.BehaviorAction = (_) =>
_options.BehaviorGenerator = (_) =>
{
cts.Cancel();
_behaviorActionExecuted = true;
_behaviorGeneratorExecuted = true;
return default;
};

Expand All @@ -134,7 +134,7 @@ public async Task Should_not_execute_user_delegate_when_it_was_cancelled_running
.ThrowAsync<OperationCanceledException>();

_userDelegateExecuted.Should().BeFalse();
_behaviorActionExecuted.Should().BeTrue();
_behaviorGeneratorExecuted.Should().BeTrue();
_onBehaviorInjectedExecuted.Should().BeFalse();
}

Expand Down