Skip to content

Commit

Permalink
Potential fix for AOT compilation
Browse files Browse the repository at this point in the history
A potential fix for #1732 using `RuntimeFeature.IsDynamicCodeSupported` to guard against the allocation regression that avoiding the infinite generic recursion causes through boxing.
  • Loading branch information
martincostello committed Oct 28, 2023
1 parent 56ffa14 commit dd2fd54
Showing 1 changed file with 57 additions and 2 deletions.
59 changes: 57 additions & 2 deletions src/Polly.Core/Utils/Pipeline/CompositeComponent.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Polly.Telemetry;
using System.Runtime.CompilerServices;
using Polly.Telemetry;

namespace Polly.Utils.Pipeline;

Expand Down Expand Up @@ -128,11 +129,26 @@ private sealed class DelegatingComponent : PipelineComponent

public PipelineComponent? Next { get; set; }

public override ValueTask DisposeAsync() => default;

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 ? ExecuteCoreImpl(callback, context, state) : ExecuteCoreAot(callback, context, state);
#else
return ExecuteCoreImpl(callback, context, state);
#endif
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ValueTask<Outcome<TResult>> ExecuteCoreImpl<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state)
{
return _component.ExecuteCore(
static (context, state) =>
{
Expand All @@ -147,6 +163,45 @@ private sealed class DelegatingComponent : PipelineComponent
(Next, callback, state));
}

public override ValueTask DisposeAsync() => default;
#if NET6_0_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ValueTask<Outcome<TResult>> ExecuteCoreAot<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;
if (context.CancellationToken.IsCancellationRequested)
{
return Outcome.FromExceptionAsValueTask<TResult>(new OperationCanceledException(context.CancellationToken).TrySetStackTrace());
}
return wrapper.Next.ExecuteCore(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
}
}

0 comments on commit dd2fd54

Please sign in to comment.