-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move `DelegatingComponent` out from being a nested type.
- Loading branch information
1 parent
7a6d72e
commit 5e3c076
Showing
4 changed files
with
151 additions
and
149 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
test/Polly.Core.Tests/Utils/Pipeline/DelegatingComponentTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |