From 5e3c0765dc7113833beea7f31b883f1559d2703f Mon Sep 17 00:00:00 2001 From: martincostello Date: Mon, 30 Oct 2023 12:05:34 +0000 Subject: [PATCH] Un-nest DelegatingComponent Move `DelegatingComponent` out from being a nested type. --- .../Utils/Pipeline/CompositeComponent.cs | 94 +------------------ .../Utils/Pipeline/DelegatingComponent.cs | 94 +++++++++++++++++++ .../CompositePipelineComponentTests.cs | 56 ----------- .../Pipeline/DelegatingComponentTests.cs | 56 +++++++++++ 4 files changed, 151 insertions(+), 149 deletions(-) create mode 100644 src/Polly.Core/Utils/Pipeline/DelegatingComponent.cs create mode 100644 test/Polly.Core.Tests/Utils/Pipeline/DelegatingComponentTests.cs diff --git a/src/Polly.Core/Utils/Pipeline/CompositeComponent.cs b/src/Polly.Core/Utils/Pipeline/CompositeComponent.cs index 7112f3942a5..1a6c47a089b 100644 --- a/src/Polly.Core/Utils/Pipeline/CompositeComponent.cs +++ b/src/Polly.Core/Utils/Pipeline/CompositeComponent.cs @@ -1,6 +1,4 @@ -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using Polly.Telemetry; +using Polly.Telemetry; namespace Polly.Utils.Pipeline; @@ -118,94 +116,4 @@ private async ValueTask> ExecuteCoreWithTelemetry - /// A component that delegates the execution to the next component in the chain. - /// - internal sealed class DelegatingComponent : PipelineComponent - { - private readonly PipelineComponent _component; - - public DelegatingComponent(PipelineComponent component) => _component = component; - - public PipelineComponent? Next { get; set; } - - public override ValueTask DisposeAsync() => default; - - [ExcludeFromCodeCoverage] - internal override ValueTask> ExecuteCore( - Func>> callback, - ResilienceContext context, - TState state) - { -#if NET6_0_OR_GREATER - return RuntimeFeature.IsDynamicCodeSupported ? ExecuteComponent(callback, context, state) : ExecuteComponentAot(callback, context, state); -#else - return ExecuteComponent(callback, context, state); -#endif - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ValueTask> ExecuteNext( - PipelineComponent next, - Func>> callback, - ResilienceContext context, - TState state) - { - if (context.CancellationToken.IsCancellationRequested) - { - return Outcome.FromExceptionAsValueTask(new OperationCanceledException(context.CancellationToken).TrySetStackTrace()); - } - - return next.ExecuteCore(callback, context, state); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ValueTask> ExecuteComponent( - Func>> callback, - ResilienceContext context, - TState state) - { - return _component.ExecuteCore( - static (context, state) => ExecuteNext(state.Next!, state.callback, context, state.state), - context, - (Next, callback, state)); - } - -#if NET6_0_OR_GREATER - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ValueTask> ExecuteComponentAot( - Func>> callback, - ResilienceContext context, - TState state) - { - // Custom state object is used to cast the callback and state to prevent infinite - // generic type recursion warning IL3054 when referenced in a native AoT application. - // See https://github.com/App-vNext/Polly/issues/1732 for further context. - return _component.ExecuteCore( - static (context, wrapper) => - { - var callback = (Func>>)wrapper.Callback; - var state = (TState)wrapper.State; - return ExecuteNext(wrapper.Next, callback, context, state); - }, - context, - new StateWrapper(Next!, callback, state!)); - } - - private struct StateWrapper - { - public StateWrapper(PipelineComponent next, object callback, object state) - { - Next = next; - Callback = callback; - State = state; - } - - public PipelineComponent Next; - public object Callback; - public object State; - } -#endif - } } diff --git a/src/Polly.Core/Utils/Pipeline/DelegatingComponent.cs b/src/Polly.Core/Utils/Pipeline/DelegatingComponent.cs new file mode 100644 index 00000000000..403ee12ea9d --- /dev/null +++ b/src/Polly.Core/Utils/Pipeline/DelegatingComponent.cs @@ -0,0 +1,94 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace Polly.Utils.Pipeline; + +/// +/// A component that delegates the execution to the next component in the chain. +/// +internal sealed class DelegatingComponent : PipelineComponent +{ + private readonly PipelineComponent _component; + + public DelegatingComponent(PipelineComponent component) => _component = component; + + public PipelineComponent? Next { get; set; } + + public override ValueTask DisposeAsync() => default; + + [ExcludeFromCodeCoverage] + internal override ValueTask> ExecuteCore( + Func>> callback, + ResilienceContext context, + TState state) + { +#if NET6_0_OR_GREATER + return RuntimeFeature.IsDynamicCodeSupported ? ExecuteComponent(callback, context, state) : ExecuteComponentAot(callback, context, state); +#else + return ExecuteComponent(callback, context, state); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ValueTask> ExecuteNext( + PipelineComponent next, + Func>> callback, + ResilienceContext context, + TState state) + { + if (context.CancellationToken.IsCancellationRequested) + { + return Outcome.FromExceptionAsValueTask(new OperationCanceledException(context.CancellationToken).TrySetStackTrace()); + } + + return next.ExecuteCore(callback, context, state); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ValueTask> ExecuteComponent( + Func>> callback, + ResilienceContext context, + TState state) + { + return _component.ExecuteCore( + static (context, state) => ExecuteNext(state.Next!, state.callback, context, state.state), + context, + (Next, callback, state)); + } + +#if NET6_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ValueTask> ExecuteComponentAot( + Func>> callback, + ResilienceContext context, + TState state) + { + // Custom state object is used to cast the callback and state to prevent infinite + // generic type recursion warning IL3054 when referenced in a native AoT application. + // See https://github.com/App-vNext/Polly/issues/1732 for further context. + return _component.ExecuteCore( + static (context, wrapper) => + { + var callback = (Func>>)wrapper.Callback; + var state = (TState)wrapper.State; + return ExecuteNext(wrapper.Next, callback, context, state); + }, + context, + new StateWrapper(Next!, callback, state!)); + } + + private struct StateWrapper + { + public StateWrapper(PipelineComponent next, object callback, object state) + { + Next = next; + Callback = callback; + State = state; + } + + public PipelineComponent Next; + public object Callback; + public object State; + } +#endif +} diff --git a/test/Polly.Core.Tests/Utils/Pipeline/CompositePipelineComponentTests.cs b/test/Polly.Core.Tests/Utils/Pipeline/CompositePipelineComponentTests.cs index 586dedb1eab..50688190d5e 100644 --- a/test/Polly.Core.Tests/Utils/Pipeline/CompositePipelineComponentTests.cs +++ b/test/Polly.Core.Tests/Utils/Pipeline/CompositePipelineComponentTests.cs @@ -140,62 +140,6 @@ public async Task DisposeAsync_EnsureInnerComponentsDisposed() await b.Received(1).DisposeAsync(); } - [Fact] - public async Task ExecuteComponent_ReturnsCorrectResult() - { - var component = new CallbackComponent(); - var next = new CallbackComponent(); - var context = ResilienceContextPool.Shared.Get(); - var state = 1; - - var composite = new CompositeComponent.DelegatingComponent(component) - { - Next = next, - }; - - var actual = await composite.ExecuteComponent( - async static (_, state) => await Outcome.FromResultAsValueTask(state + 1), - context, - state); - - actual.Should().NotBeNull(); - actual.Result.Should().Be(2); - } - -#if NET6_0_OR_GREATER - [Fact] - public async Task ExecuteComponentAot_ReturnsCorrectResult() - { - var component = new CallbackComponent(); - var next = new CallbackComponent(); - var context = ResilienceContextPool.Shared.Get(); - var state = 1; - - var composite = new CompositeComponent.DelegatingComponent(component) - { - Next = next, - }; - - var actual = await composite.ExecuteComponentAot( - async static (_, state) => await Outcome.FromResultAsValueTask(state + 1), - context, - state); - - actual.Should().NotBeNull(); - actual.Result.Should().Be(2); - } -#endif - - private sealed class CallbackComponent : PipelineComponent - { - public override ValueTask DisposeAsync() => default; - - internal override ValueTask> ExecuteCore( - Func>> callback, - ResilienceContext context, - TState state) => callback(context, state); - } - private CompositeComponent CreateSut(PipelineComponent[] components, TimeProvider? timeProvider = null) { return (CompositeComponent)PipelineComponentFactory.CreateComposite(components, _telemetry, timeProvider ?? Substitute.For()); diff --git a/test/Polly.Core.Tests/Utils/Pipeline/DelegatingComponentTests.cs b/test/Polly.Core.Tests/Utils/Pipeline/DelegatingComponentTests.cs new file mode 100644 index 00000000000..e2500c6b66e --- /dev/null +++ b/test/Polly.Core.Tests/Utils/Pipeline/DelegatingComponentTests.cs @@ -0,0 +1,56 @@ +using Polly.Utils.Pipeline; + +namespace Polly.Core.Tests.Utils.Pipeline; + +public static class DelegatingComponentTests +{ + [Fact] + public static async Task ExecuteComponent_ReturnsCorrectResult() + { + await using var component = new CallbackComponent(); + var next = new CallbackComponent(); + var context = ResilienceContextPool.Shared.Get(); + var state = 1; + + await using var delegating = new DelegatingComponent(component) { Next = next }; + + var actual = await delegating.ExecuteComponent( + async static (_, state) => await Outcome.FromResultAsValueTask(state + 1), + context, + state); + + actual.Should().NotBeNull(); + actual.Result.Should().Be(2); + } + +#if NET6_0_OR_GREATER + [Fact] + public static async Task ExecuteComponentAot_ReturnsCorrectResult() + { + await using var component = new CallbackComponent(); + var next = new CallbackComponent(); + var context = ResilienceContextPool.Shared.Get(); + var state = 1; + + await using var delegating = new DelegatingComponent(component) { Next = next }; + + var actual = await delegating.ExecuteComponentAot( + async static (_, state) => await Outcome.FromResultAsValueTask(state + 1), + context, + state); + + actual.Should().NotBeNull(); + actual.Result.Should().Be(2); + } +#endif + + private sealed class CallbackComponent : PipelineComponent + { + public override ValueTask DisposeAsync() => default; + + internal override ValueTask> ExecuteCore( + Func>> callback, + ResilienceContext context, + TState state) => callback(context, state); + } +}