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 v8 feedback #1682

Merged
merged 8 commits into from
Oct 22, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,19 @@ internal static class BehaviorPipelineBuilderExtensions
/// </summary>
/// <typeparam name="TBuilder">The builder type.</typeparam>
/// <param name="builder">The builder instance.</param>
/// <param name="enabled">A value that indicates whether or not the chaos strategy is enabled for a given execution.</param>
/// <param name="injectionRate">The injection rate for a given execution, which the value should be between [0, 1] (inclusive).</param>
/// <param name="behavior">The behavior to be injected.</param>
/// <returns>The same builder instance.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> is <see langword="null"/>.</exception>
/// <exception cref="ValidationException">Thrown when the options produced from the arguments are invalid.</exception>
public static TBuilder AddChaosBehavior<TBuilder>(this TBuilder builder, bool enabled, double injectionRate, Func<ValueTask> behavior)
public static TBuilder AddChaosBehavior<TBuilder>(this TBuilder builder, double injectionRate, Func<ValueTask> behavior)
where TBuilder : ResiliencePipelineBuilderBase
{
Guard.NotNull(builder);

return builder.AddChaosBehavior(new BehaviorStrategyOptions
{
Enabled = enabled,
Enabled = true,
InjectionRate = injectionRate,
BehaviorAction = (_) => behavior()
});
Expand Down
60 changes: 60 additions & 0 deletions src/Polly.Core/Simmy/Fault/FaultChaosStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Polly.Telemetry;

namespace Polly.Simmy.Fault;
vany0114 marked this conversation as resolved.
Show resolved Hide resolved

internal class FaultChaosStrategy : MonkeyStrategy
{
private readonly ResilienceStrategyTelemetry _telemetry;

public FaultChaosStrategy(FaultStrategyOptions options, ResilienceStrategyTelemetry telemetry)
: base(options)
{
if (options.Fault is null && options.FaultGenerator is null)
{
throw new InvalidOperationException("Either Fault or FaultGenerator is required.");
}

_telemetry = telemetry;
Fault = options.Fault;
OnFaultInjected = options.OnFaultInjected;
FaultGenerator = options.FaultGenerator is not null ? options.FaultGenerator : (_) => new(options.Fault);
}

public Func<OnFaultInjectedArguments, ValueTask>? OnFaultInjected { get; }

public Func<FaultGeneratorArguments, ValueTask<Exception?>> FaultGenerator { get; }

public Exception? Fault { get; }

protected internal override async ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state)
{
try
{
if (await ShouldInjectAsync(context).ConfigureAwait(context.ContinueOnCapturedContext))
{
var fault = await FaultGenerator(new(context)).ConfigureAwait(context.ContinueOnCapturedContext);
if (fault is not null)
{
var args = new OnFaultInjectedArguments(context, fault);
_telemetry.Report(new(ResilienceEventSeverity.Information, FaultConstants.OnFaultInjectedEvent), context, args);

if (OnFaultInjected is not null)
{
await OnFaultInjected(args).ConfigureAwait(context.ContinueOnCapturedContext);
}

return new Outcome<TResult>(fault);
}
}

return await StrategyHelper.ExecuteCallbackSafeAsync(callback, context, state).ConfigureAwait(context.ContinueOnCapturedContext);
}
catch (OperationCanceledException e)
{
return new Outcome<TResult>(e);
}
}
}
6 changes: 6 additions & 0 deletions src/Polly.Core/Simmy/Fault/FaultConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Polly.Simmy.Fault;

internal static class FaultConstants
{
public const string OnFaultInjectedEvent = "OnFaultInjectedEvent";
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Polly.Simmy.Outcomes;
namespace Polly.Simmy.Fault;

#pragma warning disable CA1815 // Override equals and operator equals on value types

Expand Down
72 changes: 72 additions & 0 deletions src/Polly.Core/Simmy/Fault/FaultPipelineBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System.Diagnostics.CodeAnalysis;
using Polly.Simmy.Fault;

namespace Polly.Simmy;

/// <summary>
/// Extension methods for adding outcome to a <see cref="ResiliencePipelineBuilder"/>.
/// </summary>
internal static class FaultPipelineBuilderExtensions
{
/// <summary>
/// Adds a fault chaos strategy to the builder.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="injectionRate">The injection rate for a given execution, which the value should be between [0, 1] (inclusive).</param>
/// <param name="fault">The exception to inject.</param>
/// <returns>The builder instance with the retry strategy added.</returns>
public static TBuilder AddChaosFault<TBuilder>(this TBuilder builder, double injectionRate, Exception fault)
where TBuilder : ResiliencePipelineBuilderBase
{
builder.AddChaosFault(new FaultStrategyOptions
{
Enabled = true,
InjectionRate = injectionRate,
Fault = fault
});
return builder;
}

/// <summary>
/// Adds a fault chaos strategy to the builder.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="injectionRate">The injection rate for a given execution, which the value should be between [0, 1] (inclusive).</param>
/// <param name="faultGenerator">The exception generator delegate.</param>
/// <returns>The builder instance with the retry strategy added.</returns>
public static TBuilder AddChaosFault<TBuilder>(this TBuilder builder, double injectionRate, Func<Exception?> faultGenerator)
where TBuilder : ResiliencePipelineBuilderBase
{
builder.AddChaosFault(new FaultStrategyOptions
{
Enabled = true,
InjectionRate = injectionRate,
FaultGenerator = (_) => new ValueTask<Exception?>(Task.FromResult(faultGenerator()))
});
return builder;
}

/// <summary>
/// Adds a fault chaos strategy to the builder.
/// </summary>
/// <typeparam name="TBuilder">The builder type.</typeparam>
/// <param name="builder">The builder instance.</param>
/// <param name="options">The fault strategy options.</param>
/// <returns>The builder instance with the retry strategy added.</returns>
[UnconditionalSuppressMessage(
"Trimming",
"IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
Justification = "All options members preserved.")]
public static TBuilder AddChaosFault<TBuilder>(this TBuilder builder, FaultStrategyOptions options)
where TBuilder : ResiliencePipelineBuilderBase
{
Guard.NotNull(builder);
Guard.NotNull(options);

builder.AddStrategy(
context => new FaultChaosStrategy(options, context.Telemetry),
options);

return builder;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Polly.Simmy.Outcomes;
namespace Polly.Simmy.Fault;

#pragma warning disable CS8618 // Required members are not initialized in constructor since this is a DTO, default value is null

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Polly.Simmy.Outcomes;
namespace Polly.Simmy.Fault;

#pragma warning disable CA1815 // Override equals and operator equals on value types

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,19 @@ internal static class LatencyPipelineBuilderExtensions
/// </summary>
/// <typeparam name="TBuilder">The builder type.</typeparam>
/// <param name="builder">The builder instance.</param>
/// <param name="enabled">A value that indicates whether or not the chaos strategy is enabled for a given execution.</param>
/// <param name="injectionRate">The injection rate for a given execution, which the value should be between [0, 1] (inclusive).</param>
/// <param name="latency">The delay value.</param>
/// <returns>The same builder instance.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> is <see langword="null"/>.</exception>
/// <exception cref="ValidationException">Thrown when the options produced from the arguments are invalid.</exception>
public static TBuilder AddChaosLatency<TBuilder>(this TBuilder builder, bool enabled, double injectionRate, TimeSpan latency)
public static TBuilder AddChaosLatency<TBuilder>(this TBuilder builder, double injectionRate, TimeSpan latency)
where TBuilder : ResiliencePipelineBuilderBase
{
Guard.NotNull(builder);

return builder.AddChaosLatency(new LatencyStrategyOptions
{
Enabled = enabled,
Enabled = true,
InjectionRate = injectionRate,
Latency = latency
});
Expand Down
84 changes: 10 additions & 74 deletions src/Polly.Core/Simmy/Outcomes/OutcomeChaosStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,65 +8,34 @@ internal class OutcomeChaosStrategy<T> : MonkeyStrategy<T>
{
private readonly ResilienceStrategyTelemetry _telemetry;

public OutcomeChaosStrategy(FaultStrategyOptions options, ResilienceStrategyTelemetry telemetry)
: base(options)
{
if (options.Fault is null && options.FaultGenerator is null)
{
throw new InvalidOperationException("Either Fault or FaultGenerator is required.");
}

_telemetry = telemetry;
Fault = options.Fault;
OnFaultInjected = options.OnFaultInjected;
FaultGenerator = options.FaultGenerator is not null ? options.FaultGenerator : (_) => new(options.Fault);
}

public OutcomeChaosStrategy(OutcomeStrategyOptions<T> options, ResilienceStrategyTelemetry telemetry)
: base(options)
{
if (options.Outcome is null && options.OutcomeGenerator is null)
{
throw new InvalidOperationException("Either Outcome or OutcomeGenerator is required.");
}

_telemetry = telemetry;
Outcome = options.Outcome;
OnOutcomeInjected = options.OnOutcomeInjected;
OutcomeGenerator = options.OutcomeGenerator is not null ? options.OutcomeGenerator : (_) => new(options.Outcome);
OutcomeGenerator = options.OutcomeGenerator;
}

public Func<OnOutcomeInjectedArguments<T>, ValueTask>? OnOutcomeInjected { get; }

public Func<OnFaultInjectedArguments, ValueTask>? OnFaultInjected { get; }

public Func<OutcomeGeneratorArguments, ValueTask<Outcome<T>?>>? OutcomeGenerator { get; }

public Func<FaultGeneratorArguments, ValueTask<Exception?>>? FaultGenerator { get; }

public Outcome<T>? Outcome { get; }

public Exception? Fault { get; }
public Func<OutcomeGeneratorArguments, ValueTask<Outcome<T>?>> OutcomeGenerator { get; }

protected internal override async ValueTask<Outcome<T>> ExecuteCore<TState>(Func<ResilienceContext, TState, ValueTask<Outcome<T>>> callback, ResilienceContext context, TState state)
{
try
{
if (await ShouldInjectAsync(context).ConfigureAwait(context.ContinueOnCapturedContext))
{
if (FaultGenerator is not null)
{
var fault = await InjectFault(context).ConfigureAwait(context.ContinueOnCapturedContext);
if (fault is not null)
{
return new Outcome<T>(fault);
}
}
else if (OutcomeGenerator is not null)
var outcome = await OutcomeGenerator(new(context)).ConfigureAwait(context.ContinueOnCapturedContext);
var args = new OnOutcomeInjectedArguments<T>(context, outcome.Value);
_telemetry.Report(new(ResilienceEventSeverity.Information, OutcomeConstants.OnOutcomeInjectedEvent), context, args);

if (OnOutcomeInjected is not null)
{
var outcome = await InjectOutcome(context).ConfigureAwait(context.ContinueOnCapturedContext);
return new Outcome<T>(outcome.Value.Result);
await OnOutcomeInjected(args).ConfigureAwait(context.ContinueOnCapturedContext);
}

return new Outcome<T>(outcome.Value.Result);
}

return await StrategyHelper.ExecuteCallbackSafeAsync(callback, context, state).ConfigureAwait(context.ContinueOnCapturedContext);
Expand All @@ -76,37 +45,4 @@ protected internal override async ValueTask<Outcome<T>> ExecuteCore<TState>(Func
return new Outcome<T>(e);
}
}

private async ValueTask<Outcome<T>?> InjectOutcome(ResilienceContext context)
{
var outcome = await OutcomeGenerator!(new(context)).ConfigureAwait(context.ContinueOnCapturedContext);
var args = new OnOutcomeInjectedArguments<T>(context, outcome.Value);
_telemetry.Report(new(ResilienceEventSeverity.Information, OutcomeConstants.OnOutcomeInjectedEvent), context, args);

if (OnOutcomeInjected is not null)
{
await OnOutcomeInjected(args).ConfigureAwait(context.ContinueOnCapturedContext);
}

return outcome;
}

private async ValueTask<Exception?> InjectFault(ResilienceContext context)
{
var fault = await FaultGenerator!(new(context)).ConfigureAwait(context.ContinueOnCapturedContext);
if (fault is null)
{
return null;
}

var args = new OnFaultInjectedArguments(context, fault);
_telemetry.Report(new(ResilienceEventSeverity.Information, OutcomeConstants.OnFaultInjectedEvent), context, args);

if (OnFaultInjected is not null)
{
await OnFaultInjected(args).ConfigureAwait(context.ContinueOnCapturedContext);
}

return fault;
}
}
2 changes: 0 additions & 2 deletions src/Polly.Core/Simmy/Outcomes/OutcomeConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,4 @@
internal static class OutcomeConstants
{
public const string OnOutcomeInjectedEvent = "OnOutcomeInjected";

public const string OnFaultInjectedEvent = "OnFaultInjectedEvent";
}
Loading