Skip to content

Commit

Permalink
Cleanup Outcome internals and drop unused hedging and fallbacks APIs (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
martintmk committed Aug 29, 2023
1 parent 94db492 commit dd511b2
Show file tree
Hide file tree
Showing 30 changed files with 129 additions and 359 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ internal static CircuitBreakerResilienceStrategy<TResult> CreateStrategy<TResult
options.MinimumThroughput,
HealthMetrics.Create(options.SamplingDuration, context.TimeProvider));

#pragma warning disable CA2000 // Dispose objects before losing scope
var controller = new CircuitStateController<TResult>(
options.BreakDuration,
options.OnOpened,
Expand All @@ -85,6 +86,7 @@ internal static CircuitBreakerResilienceStrategy<TResult> CreateStrategy<TResult
behavior,
context.TimeProvider,
context.Telemetry);
#pragma warning restore CA2000 // Dispose objects before losing scope

return new CircuitBreakerResilienceStrategy<TResult>(
options.ShouldHandle!,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public CircuitBreakerResilienceStrategy(
_handler = handler;
_controller = controller;

stateProvider?.Initialize(() => _controller.CircuitState, () => _controller.LastHandledOutcome);
stateProvider?.Initialize(() => _controller.CircuitState);
_manualControlRegistration = manualControl?.Initialize(
async c => await _controller.IsolateCircuitAsync(c).ConfigureAwait(c.ContinueOnCapturedContext),
async c => await _controller.CloseCircuitAsync(c).ConfigureAwait(c.ContinueOnCapturedContext));
Expand Down
11 changes: 1 addition & 10 deletions src/Polly.Core/CircuitBreaker/CircuitBreakerStateProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@ namespace Polly.CircuitBreaker;
public sealed class CircuitBreakerStateProvider
{
private Func<CircuitState>? _circuitStateProvider;
private Func<Outcome<object>?>? _lastHandledOutcomeProvider;

internal void Initialize(Func<CircuitState> circuitStateProvider, Func<Outcome<object>?> lastHandledOutcomeProvider)
internal void Initialize(Func<CircuitState> circuitStateProvider)
{
if (_circuitStateProvider != null)
{
throw new InvalidOperationException($"This instance of '{nameof(CircuitBreakerStateProvider)}' is already initialized and cannot be used in a different circuit-breaker strategy.");
}

_circuitStateProvider = circuitStateProvider;
_lastHandledOutcomeProvider = lastHandledOutcomeProvider;
}

/// <summary>
Expand All @@ -32,11 +30,4 @@ internal void Initialize(Func<CircuitState> circuitStateProvider, Func<Outcome<o
/// Gets the state of the underlying circuit.
/// </summary>
public CircuitState CircuitState => _circuitStateProvider?.Invoke() ?? CircuitState.Closed;

/// <summary>
/// Gets the last outcome handled by the circuit-breaker.
/// <remarks>
/// This will be null if no exceptions or results have been handled by the circuit-breaker since the circuit last closed.</remarks>
/// </summary>
internal Outcome<object>? LastHandledOutcome => _lastHandledOutcomeProvider?.Invoke();
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal sealed class CircuitStateController<T> : IDisposable
private readonly TimeSpan _breakDuration;
private DateTimeOffset _blockedUntil;
private CircuitState _circuitState = CircuitState.Closed;
private Outcome<object>? _lastOutcome;
private Outcome<T>? _lastOutcome;
private BrokenCircuitException _breakingException = new();
private bool _disposed;

Expand Down Expand Up @@ -66,7 +66,7 @@ public Exception? LastException
}
}

public Outcome<object>? LastHandledOutcome
public Outcome<T>? LastHandledOutcome
{
get
{
Expand Down Expand Up @@ -290,17 +290,17 @@ private bool PermitHalfOpenCircuitTest_NeedsLock()
return false;
}

private void SetLastHandledOutcome_NeedsLock<TResult>(Outcome<TResult> outcome)
private void SetLastHandledOutcome_NeedsLock(Outcome<T> outcome)
{
_lastOutcome = outcome.AsOutcome();
_lastOutcome = outcome;

if (outcome.Exception is Exception exception)
{
_breakingException = new BrokenCircuitException(BrokenCircuitException.DefaultMessage, exception);
}
else if (outcome.TryGetResult(out var result))
{
_breakingException = new BrokenCircuitException<TResult>(BrokenCircuitException.DefaultMessage, result!);
_breakingException = new BrokenCircuitException<T>(BrokenCircuitException.DefaultMessage, result!);
}
}

Expand Down
9 changes: 1 addition & 8 deletions src/Polly.Core/Fallback/FallbackHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@ internal sealed record class FallbackHandler<T>(
Func<FallbackPredicateArguments<T>, ValueTask<bool>> ShouldHandle,
Func<FallbackActionArguments<T>, ValueTask<Outcome<T>>> ActionGenerator)
{
public async ValueTask<Outcome<TResult>> GetFallbackOutcomeAsync<TResult>(FallbackActionArguments<T> args)
{
var copiedArgs = new FallbackActionArguments<T>(
args.Context,
args.Outcome.AsOutcome<T>());

return (await ActionGenerator(copiedArgs).ConfigureAwait(args.Context.ContinueOnCapturedContext)).AsOutcome<TResult>();
}
public ValueTask<Outcome<T>> GetFallbackOutcomeAsync(FallbackActionArguments<T> args) => ActionGenerator(args);
}

Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,6 @@ public static class FallbackResiliencePipelineBuilderExtensions
return builder.AddStrategy(context => CreateFallback(context, options), options);
}

/// <summary>
/// Adds a fallback resilience strategy with the provided options to the builder.
/// </summary>
/// <param name="builder">The resilience pipeline builder.</param>
/// <param name="options">The options to configure the fallback resilience strategy.</param>
/// <returns>The builder instance with the fallback strategy added.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
/// <exception cref="ValidationException">Thrown when <paramref name="options"/> are invalid.</exception>
[UnconditionalSuppressMessage(
"Trimming",
"IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
Justification = "All options members preserved.")]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(FallbackStrategyOptions))]
internal static ResiliencePipelineBuilder AddFallback(this ResiliencePipelineBuilder builder, FallbackStrategyOptions options)
{
Guard.NotNull(builder);
Guard.NotNull(options);

return builder.AddStrategy(context => CreateFallback(context, options), options);
}

private static ResilienceStrategy<TResult> CreateFallback<TResult>(
StrategyBuilderContext context,
FallbackStrategyOptions<TResult> options)
Expand Down
2 changes: 1 addition & 1 deletion src/Polly.Core/Fallback/FallbackResilienceStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ protected internal override async ValueTask<Outcome<T>> ExecuteCore<TState>(Func

try
{
return await _handler.GetFallbackOutcomeAsync<T>(new FallbackActionArguments<T>(context, outcome)).ConfigureAwait(context.ContinueOnCapturedContext);
return await _handler.GetFallbackOutcomeAsync(new FallbackActionArguments<T>(context, outcome)).ConfigureAwait(context.ContinueOnCapturedContext);
}
catch (Exception e)
{
Expand Down
9 changes: 0 additions & 9 deletions src/Polly.Core/Fallback/FallbackStrategyOptions.cs

This file was deleted.

11 changes: 6 additions & 5 deletions src/Polly.Core/Hedging/Controller/HedgingExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public async ValueTask<ExecutionInfo<T>> LoadExecutionAsync<TState>(
{
if (LoadedTasks >= _maxAttempts)
{
return CreateExecutionInfoWhenNoExecution<T>();
return CreateExecutionInfoWhenNoExecution();
}

// determine what type of task we are creating
Expand All @@ -77,7 +77,7 @@ public async ValueTask<ExecutionInfo<T>> LoadExecutionAsync<TState>(
else
{
_executionPool.Return(execution);
return CreateExecutionInfoWhenNoExecution<T>();
return CreateExecutionInfoWhenNoExecution();
}
}

Expand Down Expand Up @@ -148,17 +148,18 @@ public async ValueTask DisposeAsync()
return TryRemoveExecutedTask();
}

private ExecutionInfo<TResult> CreateExecutionInfoWhenNoExecution<TResult>()
private ExecutionInfo<T> CreateExecutionInfoWhenNoExecution()
{
// if there are no more executing tasks we need to check finished ones
if (_executingTasks.Count == 0)
{
var finishedExecution = _tasks.First(static t => t.ExecutionTaskSafe!.IsCompleted);
finishedExecution.AcceptOutcome();
return new ExecutionInfo<TResult>(null, false, finishedExecution.Outcome.AsOutcome<TResult>());

return new ExecutionInfo<T>(null, false, finishedExecution.Outcome);
}

return new ExecutionInfo<TResult>(null, false, null);
return new ExecutionInfo<T>(null, false, null);
}

private Task<Task> WaitForTaskCompetitionAsync()
Expand Down
41 changes: 7 additions & 34 deletions src/Polly.Core/Hedging/Controller/HedgingHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,17 @@ namespace Polly.Hedging.Utils;

internal sealed record class HedgingHandler<T>(
Func<HedgingPredicateArguments<T>, ValueTask<bool>> ShouldHandle,
Func<HedgingActionGeneratorArguments<T>, Func<ValueTask<Outcome<T>>>?> ActionGenerator,
bool IsGeneric)
Func<HedgingActionGeneratorArguments<T>, Func<ValueTask<Outcome<T>>>?> ActionGenerator)
{
public Func<ValueTask<Outcome<T>>>? GenerateAction(HedgingActionGeneratorArguments<T> args)
{
if (IsGeneric)
{
var copiedArgs = new HedgingActionGeneratorArguments<T>(
args.PrimaryContext,
args.ActionContext,
args.AttemptNumber,
args.Callback);
var copiedArgs = new HedgingActionGeneratorArguments<T>(
args.PrimaryContext,
args.ActionContext,
args.AttemptNumber,
args.Callback);

return ActionGenerator(copiedArgs);
}

return CreateNonGenericAction(args);
}

private Func<ValueTask<Outcome<T>>>? CreateNonGenericAction(HedgingActionGeneratorArguments<T> args)
{
var generator = (Func<HedgingActionGeneratorArguments<object>, Func<ValueTask<Outcome<object>>>?>)(object)ActionGenerator;
var action = generator(new HedgingActionGeneratorArguments<object>(args.PrimaryContext, args.ActionContext, args.AttemptNumber, async context =>
{
var outcome = await args.Callback(context).ConfigureAwait(context.ContinueOnCapturedContext);
return outcome.AsOutcome();
}));

if (action is null)
{
return null;
}

return async () =>
{
var outcome = await action().ConfigureAwait(args.ActionContext.ContinueOnCapturedContext);
return outcome.AsOutcome<T>();
};
return ActionGenerator(copiedArgs);
}
}

4 changes: 2 additions & 2 deletions src/Polly.Core/Hedging/Controller/TaskExecution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public TaskExecution(HedgingHandler<T> handler, CancellationTokenSourcePool canc
/// </remarks>
public Task? ExecutionTaskSafe { get; private set; }

public Outcome<object> Outcome { get; private set; }
public Outcome<T> Outcome { get; private set; }

public bool IsHandled { get; private set; }

Expand Down Expand Up @@ -225,7 +225,7 @@ private async Task ExecutePrimaryActionAsync<TState>(Func<ResilienceContext, TSt
private async Task UpdateOutcomeAsync(Outcome<T> outcome)
{
var args = new HedgingPredicateArguments<T>(Context, outcome);
Outcome = outcome.AsOutcome();
Outcome = outcome;
IsHandled = await _handler.ShouldHandle(args).ConfigureAwait(Context.ContinueOnCapturedContext);
TelemetryUtil.ReportExecutionAttempt(_telemetry, Context, outcome, AttemptNumber, ExecutionTime, IsHandled);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,39 +30,12 @@ public static class HedgingResiliencePipelineBuilderExtensions
Guard.NotNull(builder);
Guard.NotNull(options);

return builder.AddStrategy(context => CreateHedgingStrategy(context, options, isGeneric: true), options);
return builder.AddStrategy(context => CreateHedgingStrategy(context, options), options);
}

/// <summary>
/// Adds a hedging with the provided options to the builder.
/// </summary>
/// <param name="builder">The resilience pipeline builder.</param>
/// <param name="options">The options to configure the hedging.</param>
/// <returns>The builder instance with the hedging added.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> or <paramref name="options"/> is <see langword="null"/>.</exception>
/// <exception cref="ValidationException">Thrown when <paramref name="options"/> are invalid.</exception>
[UnconditionalSuppressMessage(
"Trimming",
"IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
Justification = "All options members preserved.")]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(HedgingStrategyOptions))]
internal static ResiliencePipelineBuilder AddHedging(this ResiliencePipelineBuilder builder, HedgingStrategyOptions options)
{
Guard.NotNull(builder);
Guard.NotNull(options);

return builder.AddStrategy(context => CreateHedgingStrategy(context, options, isGeneric: false), options);
}

private static HedgingResilienceStrategy<TResult> CreateHedgingStrategy<TResult>(
StrategyBuilderContext context,
HedgingStrategyOptions<TResult> options,
bool isGeneric)
private static HedgingResilienceStrategy<TResult> CreateHedgingStrategy<TResult>(StrategyBuilderContext context, HedgingStrategyOptions<TResult> options)
{
var handler = new HedgingHandler<TResult>(
options.ShouldHandle!,
options.ActionGenerator,
IsGeneric: isGeneric);
var handler = new HedgingHandler<TResult>(options.ShouldHandle!, options.ActionGenerator);

return new HedgingResilienceStrategy<TResult>(
options.Delay,
Expand Down
2 changes: 1 addition & 1 deletion src/Polly.Core/Hedging/HedgingResilienceStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ await HandleOnHedgingAsync(
continue;
}

outcome = execution.Outcome.AsOutcome<T>();
outcome = execution.Outcome;

if (!execution.IsHandled)
{
Expand Down
9 changes: 0 additions & 9 deletions src/Polly.Core/Hedging/HedgingStrategyOptions.cs

This file was deleted.

28 changes: 1 addition & 27 deletions src/Polly.Core/Outcome.TResult.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#pragma warning disable CA1815 // Override equals and operator equals on value types

using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;

namespace Polly;
Expand All @@ -20,7 +19,7 @@ internal Outcome(Exception exception)
internal Outcome(TResult? result)
: this() => Result = result;

private Outcome(ExceptionDispatchInfo exceptionDispatchInfo)
internal Outcome(ExceptionDispatchInfo exceptionDispatchInfo)
: this() => ExceptionDispatchInfo = Guard.NotNull(exceptionDispatchInfo);

/// <summary>
Expand Down Expand Up @@ -92,29 +91,4 @@ internal TResult GetResultOrRethrow()
return Result!;
}

internal Outcome<object> AsOutcome() => AsOutcome<object>();

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Outcome<T> AsOutcome<T>()
{
if (ExceptionDispatchInfo is not null)
{
return new Outcome<T>(ExceptionDispatchInfo);
}

if (Result is null)
{
return new Outcome<T>(default(T));
}

if (typeof(T) == typeof(TResult))
{
var result = Result;

// We can use the unsafe cast here because we know for sure these two types are the same
return new Outcome<T>(Unsafe.As<TResult, T>(ref result));
}

return new Outcome<T>((T)(object)Result);
}
}
1 change: 0 additions & 1 deletion src/Polly.Core/Outcome.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,4 @@ public static ValueTask<Outcome<TResult>> FromExceptionAsTask<TResult>(Exception
internal static Outcome<VoidResult> Void => FromResult(VoidResult.Instance);

internal static Outcome<VoidResult> FromException(Exception exception) => FromException<VoidResult>(exception);

}
Loading

0 comments on commit dd511b2

Please sign in to comment.