Skip to content

Commit

Permalink
Introduce NonReactiveResilienceStrategy (#1476)
Browse files Browse the repository at this point in the history
  • Loading branch information
martintmk authored Aug 10, 2023
1 parent 5f253dc commit 3663510
Show file tree
Hide file tree
Showing 46 changed files with 243 additions and 185 deletions.
2 changes: 1 addition & 1 deletion bench/Polly.Core.Benchmarks/TelemetryBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private ResilienceStrategy Build(CompositeStrategyBuilder builder)
return builder.Build();
}

private class TelemetryEventStrategy : ResilienceStrategy
private class TelemetryEventStrategy : NonReactiveResilienceStrategy
{
private readonly ResilienceStrategyTelemetry _telemetry;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Polly.Core.Benchmarks.Utils;

internal class EmptyResilienceStrategy : ResilienceStrategy
internal class EmptyResilienceStrategy : NonReactiveResilienceStrategy
{
protected override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
Expand Down
4 changes: 2 additions & 2 deletions bench/Polly.Core.Benchmarks/Utils/Helper.CircuitBreaker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static object CreateOpenedCircuitBreaker(PollyVersion version, bool handl

if (handleOutcome)
{
builder.AddStrategy(new OutcomeHandlingStrategy());
builder.AddStrategy(_ => new OutcomeHandlingStrategy(), new EmptyResilienceOptions());
}

var strategy = builder.AddCircuitBreaker(options).Build();
Expand Down Expand Up @@ -64,7 +64,7 @@ public static object CreateCircuitBreaker(PollyVersion technology)
};
}

private class OutcomeHandlingStrategy : ResilienceStrategy
private class OutcomeHandlingStrategy : NonReactiveResilienceStrategy
{
protected 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 @@ -10,7 +10,7 @@ internal static partial class Helper
{
for (var i = 0; i < count; i++)
{
builder.AddStrategy(new EmptyResilienceStrategy());
builder.AddStrategy(_ => new EmptyResilienceStrategy(), new EmptyResilienceOptions());
}
}),
_ => throw new NotSupportedException()
Expand Down
7 changes: 4 additions & 3 deletions src/Polly.Core/CompositeStrategyBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public static TBuilder AddStrategy<TBuilder>(this TBuilder builder, ResilienceSt
Guard.NotNull(builder);
Guard.NotNull(strategy);

return builder.AddStrategy(_ => strategy, EmptyOptions.Instance);
builder.AddStrategyCore(_ => strategy, EmptyOptions.Instance);
return builder;
}

/// <summary>
Expand Down Expand Up @@ -59,14 +60,14 @@ public static CompositeStrategyBuilder<TResult> AddStrategy<TResult>(this Compos
/// <exception cref="InvalidOperationException">Thrown when this builder was already used to create a strategy. The builder cannot be modified after it has been used.</exception>
/// <exception cref="ValidationException">Thrown when <paramref name="options"/> is invalid.</exception>
[RequiresUnreferencedCode(Constants.OptionsValidation)]
public static TBuilder AddStrategy<TBuilder>(this TBuilder builder, Func<StrategyBuilderContext, ResilienceStrategy> factory, ResilienceStrategyOptions options)
public static TBuilder AddStrategy<TBuilder>(this TBuilder builder, Func<StrategyBuilderContext, NonReactiveResilienceStrategy> factory, ResilienceStrategyOptions options)
where TBuilder : CompositeStrategyBuilderBase
{
Guard.NotNull(builder);
Guard.NotNull(factory);
Guard.NotNull(options);

builder.AddStrategyCore(factory, options);
builder.AddStrategyCore(context => new NonReactiveResilienceStrategyBridge(factory(context)), options);
return builder;
}

Expand Down
36 changes: 36 additions & 0 deletions src/Polly.Core/NonReactiveResilienceStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace Polly;

/// <summary>
/// Base class for all non-reactive resilience strategies.
/// </summary>
public abstract class NonReactiveResilienceStrategy
{
/// <summary>
/// An implementation of a non-reactive resilience strategy that executes the specified <paramref name="callback"/>.
/// </summary>
/// <typeparam name="TResult">The type of result returned by the callback.</typeparam>
/// <typeparam name="TState">The type of state associated with the callback.</typeparam>
/// <param name="callback">The user-provided callback.</param>
/// <param name="context">The context associated with the callback.</param>
/// <param name="state">The state associated with the callback.</param>
/// <returns>
/// An instance of a pending <see cref="ValueTask"/> for asynchronous executions or a completed <see cref="ValueTask"/> task for synchronous executions.
/// </returns>
/// <remarks>
/// <strong>This method is called for both synchronous and asynchronous execution flows.</strong>
/// <para>
/// You can use <see cref="ResilienceContext.IsSynchronous"/> to determine whether <paramref name="callback"/> is synchronous or asynchronous.
/// This is useful when the custom strategy behaves differently in each execution flow. In general, for most strategies, the implementation
/// is the same for both execution flows.
/// See <seealso href="https://github.com/App-vNext/Polly/blob/main/src/Polly.Core/README.md#about-synchronous-and-asynchronous-executions"/> for more details.
/// </para>
/// <para>
/// The provided callback never throws an exception. Instead, the exception is captured and converted to an <see cref="Outcome{TResult}"/>.
/// Similarly, do not throw exceptions from your strategy implementation. Instead, return an exception instance as <see cref="Outcome{TResult}"/>.
/// </para>
/// </remarks>
protected internal abstract ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state);
}
2 changes: 1 addition & 1 deletion src/Polly.Core/NullResilienceStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ private NullResilienceStrategy()
}

/// <inheritdoc/>
protected internal override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
internal override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state)
Expand Down
9 changes: 5 additions & 4 deletions src/Polly.Core/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
abstract Polly.ReactiveResilienceStrategy<TResult>.ExecuteCore<TState>(System.Func<Polly.ResilienceContext!, TState, System.Threading.Tasks.ValueTask<Polly.Outcome<TResult>>>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask<Polly.Outcome<TResult>>
abstract Polly.NonReactiveResilienceStrategy.ExecuteCore<TResult, TState>(System.Func<Polly.ResilienceContext!, TState, System.Threading.Tasks.ValueTask<Polly.Outcome<TResult>>>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask<Polly.Outcome<TResult>>
abstract Polly.ReactiveResilienceStrategy<TResult>.ExecuteCore<TState>(System.Func<Polly.ResilienceContext!, TState, System.Threading.Tasks.ValueTask<Polly.Outcome<TResult>>>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask<Polly.Outcome<TResult>>
abstract Polly.Registry.ResilienceStrategyProvider<TKey>.TryGetStrategy(TKey key, out Polly.ResilienceStrategy? strategy) -> bool
abstract Polly.Registry.ResilienceStrategyProvider<TKey>.TryGetStrategy<TResult>(TKey key, out Polly.ResilienceStrategy<TResult>? strategy) -> bool
abstract Polly.ResilienceContextPool.Get(string? operationKey, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Polly.ResilienceContext!
abstract Polly.ResilienceContextPool.Return(Polly.ResilienceContext! context) -> void
abstract Polly.ResilienceStrategy.ExecuteCore<TResult, TState>(System.Func<Polly.ResilienceContext!, TState, System.Threading.Tasks.ValueTask<Polly.Outcome<TResult>>>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask<Polly.Outcome<TResult>>
override Polly.Outcome<TResult>.ToString() -> string!
override Polly.Registry.ResilienceStrategyRegistry<TKey>.TryGetStrategy(TKey key, out Polly.ResilienceStrategy? strategy) -> bool
override Polly.Registry.ResilienceStrategyRegistry<TKey>.TryGetStrategy<TResult>(TKey key, out Polly.ResilienceStrategy<TResult>? strategy) -> bool
Expand Down Expand Up @@ -147,6 +147,8 @@ Polly.Hedging.OnHedgingArguments.HasOutcome.get -> bool
Polly.Hedging.OnHedgingArguments.OnHedgingArguments(int attemptNumber, bool hasOutcome, System.TimeSpan executionTime) -> void
Polly.HedgingCompositeStrategyBuilderExtensions
Polly.LegacySupport
Polly.NonReactiveResilienceStrategy
Polly.NonReactiveResilienceStrategy.NonReactiveResilienceStrategy() -> void
Polly.NullResilienceStrategy
Polly.NullResilienceStrategy<TResult>
Polly.Outcome
Expand Down Expand Up @@ -262,7 +264,6 @@ Polly.ResilienceStrategy.ExecuteAsync<TResult>(System.Func<System.Threading.Canc
Polly.ResilienceStrategy.ExecuteAsync<TState>(System.Func<Polly.ResilienceContext!, TState, System.Threading.Tasks.ValueTask>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask
Polly.ResilienceStrategy.ExecuteAsync<TState>(System.Func<TState, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask>! callback, TState state, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
Polly.ResilienceStrategy.ExecuteOutcomeAsync<TResult, TState>(System.Func<Polly.ResilienceContext!, TState, System.Threading.Tasks.ValueTask<Polly.Outcome<TResult>>>! callback, Polly.ResilienceContext! context, TState state) -> System.Threading.Tasks.ValueTask<Polly.Outcome<TResult>>
Polly.ResilienceStrategy.ResilienceStrategy() -> void
Polly.ResilienceStrategy<T>
Polly.ResilienceStrategy<T>.Execute<TResult, TState>(System.Func<Polly.ResilienceContext!, TState, TResult>! callback, Polly.ResilienceContext! context, TState state) -> TResult
Polly.ResilienceStrategy<T>.Execute<TResult, TState>(System.Func<TState, System.Threading.CancellationToken, TResult>! callback, TState state, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> TResult
Expand Down Expand Up @@ -389,7 +390,7 @@ static Polly.CircuitBreakerCompositeStrategyBuilderExtensions.AddCircuitBreaker(
static Polly.CircuitBreakerCompositeStrategyBuilderExtensions.AddCircuitBreaker<TResult>(this Polly.CompositeStrategyBuilder<TResult>! builder, Polly.CircuitBreaker.CircuitBreakerStrategyOptions<TResult>! options) -> Polly.CompositeStrategyBuilder<TResult>!
static Polly.CompositeStrategyBuilderExtensions.AddStrategy(this Polly.CompositeStrategyBuilder! builder, System.Func<Polly.StrategyBuilderContext!, Polly.ReactiveResilienceStrategy<object!>!>! factory, Polly.ResilienceStrategyOptions! options) -> Polly.CompositeStrategyBuilder!
static Polly.CompositeStrategyBuilderExtensions.AddStrategy<TBuilder>(this TBuilder! builder, Polly.ResilienceStrategy! strategy) -> TBuilder!
static Polly.CompositeStrategyBuilderExtensions.AddStrategy<TBuilder>(this TBuilder! builder, System.Func<Polly.StrategyBuilderContext!, Polly.ResilienceStrategy!>! factory, Polly.ResilienceStrategyOptions! options) -> TBuilder!
static Polly.CompositeStrategyBuilderExtensions.AddStrategy<TBuilder>(this TBuilder! builder, System.Func<Polly.StrategyBuilderContext!, Polly.NonReactiveResilienceStrategy!>! factory, Polly.ResilienceStrategyOptions! options) -> TBuilder!
static Polly.CompositeStrategyBuilderExtensions.AddStrategy<TResult>(this Polly.CompositeStrategyBuilder<TResult>! builder, Polly.ResilienceStrategy<TResult>! strategy) -> Polly.CompositeStrategyBuilder<TResult>!
static Polly.CompositeStrategyBuilderExtensions.AddStrategy<TResult>(this Polly.CompositeStrategyBuilder<TResult>! builder, System.Func<Polly.StrategyBuilderContext!, Polly.ReactiveResilienceStrategy<TResult>!>! factory, Polly.ResilienceStrategyOptions! options) -> Polly.CompositeStrategyBuilder<TResult>!
static Polly.FallbackCompositeStrategyBuilderExtensions.AddFallback<TResult>(this Polly.CompositeStrategyBuilder<TResult>! builder, Polly.Fallback.FallbackStrategyOptions<TResult>! options) -> Polly.CompositeStrategyBuilder<TResult>!
Expand Down
2 changes: 1 addition & 1 deletion src/Polly.Core/ReactiveResilienceStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
public abstract class ReactiveResilienceStrategy<TResult>
{
/// <summary>
/// An implementation of resilience strategy that executes the specified <paramref name="callback"/>.
/// An implementation of a reactive resilience strategy that executes the specified <paramref name="callback"/>.
/// </summary>
/// <typeparam name="TState">The type of state associated with the callback.</typeparam>
/// <param name="callback">The user-provided callback.</param>
Expand Down
34 changes: 6 additions & 28 deletions src/Polly.Core/ResilienceStrategy.cs
Original file line number Diff line number Diff line change
@@ -1,45 +1,23 @@
namespace Polly;

#pragma warning disable CA1031 // Do not catch general exception types

/// <summary>
/// Resilience strategy is used to execute the user-provided callbacks.
/// </summary>
/// <remarks>
/// Resilience strategy supports various types of callbacks and provides a unified way to execute them.
/// This includes overloads for synchronous and asynchronous callbacks, generic and non-generic callbacks.
/// </remarks>
public abstract partial class ResilienceStrategy
public partial class ResilienceStrategy
{
internal static ResilienceContextPool Pool => ResilienceContextPool.Shared;

internal ResilienceStrategyOptions? Options { get; set; }

/// <summary>
/// An implementation of resilience strategy that executes the specified <paramref name="callback"/>.
/// </summary>
/// <typeparam name="TResult">The type of result returned by the callback.</typeparam>
/// <typeparam name="TState">The type of state associated with the callback.</typeparam>
/// <param name="callback">The user-provided callback.</param>
/// <param name="context">The context associated with the callback.</param>
/// <param name="state">The state associated with the callback.</param>
/// <returns>
/// An instance of a pending <see cref="ValueTask"/> for asynchronous executions or a completed <see cref="ValueTask"/> task for synchronous executions.
/// </returns>
/// <remarks>
/// <strong>This method is called for both synchronous and asynchronous execution flows.</strong>
/// <para>
/// You can use <see cref="ResilienceContext.IsSynchronous"/> to determine whether <paramref name="callback"/> is synchronous or asynchronous.
/// This is useful when the custom strategy behaves differently in each execution flow. In general, for most strategies, the implementation
/// is the same for both execution flows.
/// See <seealso href="https://github.com/App-vNext/Polly/blob/main/src/Polly.Core/README.md#about-synchronous-and-asynchronous-executions"/> for more details.
/// </para>
/// <para>
/// The provided callback never throws an exception. Instead, the exception is captured and converted to an <see cref="Outcome{TResult}"/>.
/// Similarly, do not throw exceptions from your strategy implementation. Instead, return an exception instance as <see cref="Outcome{TResult}"/>.
/// </para>
/// </remarks>
protected internal abstract ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
internal ResilienceStrategy()
{
}

internal abstract ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state);
Expand Down
2 changes: 1 addition & 1 deletion src/Polly.Core/Timeout/TimeoutResilienceStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Polly.Timeout;

internal sealed class TimeoutResilienceStrategy : ResilienceStrategy
internal sealed class TimeoutResilienceStrategy : NonReactiveResilienceStrategy
{
private readonly ResilienceStrategyTelemetry _telemetry;
private readonly CancellationTokenSourcePool _cancellationTokenSourcePool;
Expand Down
4 changes: 2 additions & 2 deletions src/Polly.Core/Utils/CompositeResilienceStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ private CompositeResilienceStrategy(ResilienceStrategy first, IReadOnlyList<Resi

public IReadOnlyList<ResilienceStrategy> Strategies { get; }

protected internal override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
internal override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context, TState state)
{
Expand All @@ -78,7 +78,7 @@ private sealed class DelegatingResilienceStrategy : ResilienceStrategy

public ResilienceStrategy? Next { get; set; }

protected internal override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
internal override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state)
Expand Down
14 changes: 14 additions & 0 deletions src/Polly.Core/Utils/NonReactiveResilienceStrategyBridge.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Polly.Utils;

[DebuggerDisplay("{Strategy}")]
internal sealed class NonReactiveResilienceStrategyBridge : ResilienceStrategy
{
public NonReactiveResilienceStrategyBridge(NonReactiveResilienceStrategy strategy) => Strategy = strategy;

public NonReactiveResilienceStrategy Strategy { get; }

internal override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state) => Strategy.ExecuteCore(callback, context, state);
}
2 changes: 1 addition & 1 deletion src/Polly.Core/Utils/ReactiveResilienceStrategyBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal sealed class ReactiveResilienceStrategyBridge<T> : ResilienceStrategy

public ReactiveResilienceStrategy<T> Strategy { get; }

protected internal override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
internal override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state)
Expand Down
2 changes: 1 addition & 1 deletion src/Polly.Core/Utils/ReloadableResilienceStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public ReloadableResilienceStrategy(

public ResilienceStrategy Strategy { get; private set; }

protected internal override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
internal override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public static TBuilder ConfigureTelemetry<TBuilder>(this TBuilder builder, ILogg
/// </remarks>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(TelemetryOptions))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(TelemetryStrategyOptions))]
public static TBuilder ConfigureTelemetry<TBuilder>(this TBuilder builder, TelemetryOptions options)
where TBuilder : CompositeStrategyBuilderBase
{
Expand All @@ -62,9 +63,15 @@ public static TBuilder ConfigureTelemetry<TBuilder>(this TBuilder builder, Telem
options.ResultFormatter,
options.Enrichers.ToList());
strategies.Insert(0, telemetryStrategy);
#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
strategies.Insert(0, new CompositeStrategyBuilder().AddStrategy(_ => telemetryStrategy, new TelemetryStrategyOptions()).Build());
#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
};

return builder;
}

private sealed class TelemetryStrategyOptions : ResilienceStrategyOptions
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Polly.Telemetry;

internal sealed class TelemetryResilienceStrategy : ResilienceStrategy
internal sealed class TelemetryResilienceStrategy : NonReactiveResilienceStrategy
{
private readonly TimeProvider _timeProvider;
private readonly string? _builderName;
Expand Down
2 changes: 1 addition & 1 deletion src/Polly.RateLimiting/RateLimiterResilienceStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Polly.RateLimiting;

internal sealed class RateLimiterResilienceStrategy : ResilienceStrategy
internal sealed class RateLimiterResilienceStrategy : NonReactiveResilienceStrategy
{
private readonly ResilienceStrategyTelemetry _telemetry;

Expand Down
Loading

0 comments on commit 3663510

Please sign in to comment.