Skip to content

Commit

Permalink
Un-nest DelegatingComponent
Browse files Browse the repository at this point in the history
Move `DelegatingComponent` out from being a nested type.
  • Loading branch information
martincostello committed Oct 30, 2023
1 parent 7a6d72e commit 5e3c076
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 149 deletions.
94 changes: 1 addition & 93 deletions src/Polly.Core/Utils/Pipeline/CompositeComponent.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Polly.Telemetry;
using Polly.Telemetry;

namespace Polly.Utils.Pipeline;

Expand Down Expand Up @@ -118,94 +116,4 @@ private async ValueTask<Outcome<TResult>> ExecuteCoreWithTelemetry<TResult, TSta

return outcome;
}

/// <summary>
/// A component that delegates the execution to the next component in the chain.
/// </summary>
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<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> 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<Outcome<TResult>> ExecuteNext<TResult, TState>(
PipelineComponent next,
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state)
{
if (context.CancellationToken.IsCancellationRequested)
{
return Outcome.FromExceptionAsValueTask<TResult>(new OperationCanceledException(context.CancellationToken).TrySetStackTrace());
}

return next.ExecuteCore(callback, context, state);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ValueTask<Outcome<TResult>> ExecuteComponent<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> 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<Outcome<TResult>> ExecuteComponentAot<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> 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<ResilienceContext, TState, ValueTask<Outcome<TResult>>>)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
}
}
94 changes: 94 additions & 0 deletions src/Polly.Core/Utils/Pipeline/DelegatingComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace Polly.Utils.Pipeline;

/// <summary>
/// A component that delegates the execution to the next component in the chain.
/// </summary>
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<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> 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<Outcome<TResult>> ExecuteNext<TResult, TState>(
PipelineComponent next,
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state)
{
if (context.CancellationToken.IsCancellationRequested)
{
return Outcome.FromExceptionAsValueTask<TResult>(new OperationCanceledException(context.CancellationToken).TrySetStackTrace());
}

return next.ExecuteCore(callback, context, state);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ValueTask<Outcome<TResult>> ExecuteComponent<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> 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<Outcome<TResult>> ExecuteComponentAot<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> 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<ResilienceContext, TState, ValueTask<Outcome<TResult>>>)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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> 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<TimeProvider>());
Expand Down
56 changes: 56 additions & 0 deletions test/Polly.Core.Tests/Utils/Pipeline/DelegatingComponentTests.cs
Original file line number Diff line number Diff line change
@@ -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<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state) => callback(context, state);
}
}

0 comments on commit 5e3c076

Please sign in to comment.