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

Introduce NonReactiveResilienceStrategy #1476

Merged
merged 6 commits into from
Aug 10, 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
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