Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve AOT compilation issues #1737

Merged
merged 10 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
* Speed-up page builds by [@martincostello](https://github.com/martincostello) in https://github.com/App-vNext/Polly/pull/1753
* Hedging strategy also deep-copies context for primary execution by [@martintmk](https://github.com/martintmk) in https://github.com/App-vNext/Polly/pull/1754
* [Docs] Add diagram about hedging's context and callbacks by [@peter-csala](https://github.com/peter-csala) in https://github.com/App-vNext/Polly/pull/1751
* Resolve AOT compilation issues by [@martincostello](https://github.com/martincostello) in https://github.com/App-vNext/Polly/pull/1737

## 8.0.0

Expand Down
7 changes: 7 additions & 0 deletions Polly.sln
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Testing.Tests", "test
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snippets", "src\Snippets\Snippets.csproj", "{D812B941-79B0-4E1E-BB70-4FAE345B5234}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.AotTest", "test\Polly.AotTest\Polly.AotTest.csproj", "{84091007-CFA5-4852-AC41-0171DF039C4E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -120,6 +122,10 @@ Global
{D812B941-79B0-4E1E-BB70-4FAE345B5234}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D812B941-79B0-4E1E-BB70-4FAE345B5234}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D812B941-79B0-4E1E-BB70-4FAE345B5234}.Release|Any CPU.Build.0 = Release|Any CPU
{84091007-CFA5-4852-AC41-0171DF039C4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{84091007-CFA5-4852-AC41-0171DF039C4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84091007-CFA5-4852-AC41-0171DF039C4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{84091007-CFA5-4852-AC41-0171DF039C4E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -140,6 +146,7 @@ Global
{9AD2D6AD-56E4-49D6-B6F1-EE975D5760B9} = {B7BF406B-B06F-4025-83E6-7219C53196A6}
{D333B5CE-982D-4C11-BDAF-4217AA02306E} = {A6CC41B9-E0B9-44F8-916B-3E4A78DA3BFB}
{D812B941-79B0-4E1E-BB70-4FAE345B5234} = {B7BF406B-B06F-4025-83E6-7219C53196A6}
{84091007-CFA5-4852-AC41-0171DF039C4E} = {A6CC41B9-E0B9-44F8-916B-3E4A78DA3BFB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2E5D54CD-770A-4345-B585-1848FC2EA6F4}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ LaunchCount=2 WarmupCount=10
```
| Method | Mean | Error | StdDev | Allocated |
|------------------------------- |---------:|---------:|---------:|----------:|
| CompositeComponent_ExecuteCore | 44.37 ns | 1.994 ns | 2.923 ns | - |
| CompositeComponent_ExecuteCore | 37.44 ns | 0.713 ns | 0.952 ns | - |
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
```

BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, Windows 11 (10.0.22621.2428/22H2/2022Update/SunValley2)
12th Gen Intel Core i7-1270P, 1 CPU, 16 logical and 12 physical cores
.NET SDK 7.0.403
[Host] : .NET 7.0.13 (7.0.1323.51816), X64 RyuJIT AVX2

Job=MediumRun Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=2 WarmupCount=10

```
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------------------------------------ |---------:|---------:|---------:|------:|--------:|-------:|----------:|------------:|
| DelegatingComponent_ExecuteCore_Jit | 29.40 ns | 0.699 ns | 1.025 ns | 1.00 | 0.00 | - | - | NA |
| DelegatingComponent_ExecuteCore_Aot | 34.65 ns | 0.627 ns | 0.919 ns | 1.18 | 0.05 | 0.0025 | 24 B | NA |
38 changes: 38 additions & 0 deletions bench/Polly.Core.Benchmarks/DelegatingComponentBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Polly.Utils.Pipeline;

namespace Polly.Core.Benchmarks;

public class DelegatingComponentBenchmark : IAsyncDisposable
{
private ResilienceContext? _context;
private DelegatingComponent? _component;

[GlobalSetup]
public void Setup()
{
var first = PipelineComponent.Empty;
var second = PipelineComponent.Empty;

_component = new DelegatingComponent(first) { Next = second };
_context = ResilienceContextPool.Shared.Get();
}

public async ValueTask DisposeAsync()
{
if (_component is not null)
{
await _component.DisposeAsync().ConfigureAwait(false);
_component = null;
}

GC.SuppressFinalize(this);
}

[Benchmark(Baseline = true)]
public ValueTask<Outcome<int>> DelegatingComponent_ExecuteCore_Jit()
=> _component!.ExecuteComponent((_, state) => Outcome.FromResultAsValueTask(state), _context!, 42);

[Benchmark]
public ValueTask<Outcome<int>> DelegatingComponent_ExecuteCore_Aot()
=> _component!.ExecuteComponentAot((_, state) => Outcome.FromResultAsValueTask(state), _context!, 42);
}
28 changes: 23 additions & 5 deletions build.cake
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ Task("__Clean")

CleanDirectories(cleanDirectories);

foreach(var path in cleanDirectories) { EnsureDirectoryExists(path); }
foreach (var path in cleanDirectories) { EnsureDirectoryExists(path); }

foreach(var path in solutionPaths)
foreach (var path in solutionPaths)
{
Information("Cleaning {0}", path);

Expand All @@ -93,7 +93,7 @@ Task("__Clean")
Task("__RestoreNuGetPackages")
.Does(() =>
{
foreach(var solution in solutions)
foreach (var solution in solutions)
{
Information("Restoring NuGet Packages for {0}", solution);
DotNetRestore(solution.ToString());
Expand All @@ -103,7 +103,7 @@ Task("__RestoreNuGetPackages")
Task("__BuildSolutions")
.Does(() =>
{
foreach(var solution in solutions)
foreach (var solution in solutions)
{
Information("Building {0}", solution);

Expand All @@ -125,6 +125,23 @@ Task("__BuildSolutions")
}
});

Task("__ValidateAot")
.Does(() =>
{
var aotProject = MakeAbsolute(File("./test/Polly.AotTest/Polly.AotTest.csproj"));
var settings = new DotNetPublishSettings
{
Configuration = configuration,
Verbosity = DotNetVerbosity.Minimal,
MSBuildSettings = new DotNetMSBuildSettings
{
TreatAllWarningsAs = MSBuildTreatAllWarningsAs.Error,
},
};

DotNetPublish(aotProject.ToString(), settings);
});

Task("__RunTests")
.Does(() =>
{
Expand All @@ -137,7 +154,7 @@ Task("__RunTests")

var projects = GetFiles("./test/**/*.csproj");

foreach(var proj in projects)
foreach (var proj in projects)
{
DotNetTest(proj.FullPath, new DotNetTestSettings
{
Expand Down Expand Up @@ -252,6 +269,7 @@ Task("Build")
.IsDependentOn("__RestoreNuGetPackages")
.IsDependentOn("__ValidateDocs")
.IsDependentOn("__BuildSolutions")
.IsDependentOn("__ValidateAot")
.IsDependentOn("__RunTests")
.IsDependentOn("__RunMutationTests")
.IsDependentOn("__CreateNuGetPackages");
Expand Down
1 change: 1 addition & 0 deletions src/Polly.Core/Polly.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<EnableAotAnalyzer>true</EnableAotAnalyzer>
<EnableSingleFileAnalyzer>true</EnableSingleFileAnalyzer>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<IsAotCompatible>true</IsAotCompatible>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>

Expand Down
33 changes: 0 additions & 33 deletions src/Polly.Core/Utils/Pipeline/CompositeComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,37 +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>
private sealed class DelegatingComponent : PipelineComponent
{
private readonly PipelineComponent _component;

public DelegatingComponent(PipelineComponent component) => _component = component;

public PipelineComponent? Next { get; set; }

internal override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
ResilienceContext context,
TState state)
{
return _component.ExecuteCore(
static (context, state) =>
{
if (context.CancellationToken.IsCancellationRequested)
{
return Outcome.FromExceptionAsValueTask<TResult>(new OperationCanceledException(context.CancellationToken).TrySetStackTrace());
}

return state.Next!.ExecuteCore(state.callback, context, state.state);
},
context,
(Next, callback, state));
}

public override ValueTask DisposeAsync() => default;
}
}
82 changes: 82 additions & 0 deletions src/Polly.Core/Utils/Pipeline/DelegatingComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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;
martincostello marked this conversation as resolved.
Show resolved Hide resolved

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 readonly record struct StateWrapper(PipelineComponent Next, object Callback, object State);
#endif
}
2 changes: 1 addition & 1 deletion src/Polly.Core/Utils/Pipeline/PipelineComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ internal Outcome<TResult> ExecuteCoreSync<TResult, TState>(

public abstract ValueTask DisposeAsync();

private class NullComponent : PipelineComponent
private sealed class NullComponent : PipelineComponent
{
internal override ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback, ResilienceContext context, TState state)
=> callback(context, state);
Expand Down
1 change: 1 addition & 0 deletions src/Polly.Extensions/Polly.Extensions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<EnableAotAnalyzer>true</EnableAotAnalyzer>
<EnableSingleFileAnalyzer>true</EnableSingleFileAnalyzer>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<IsAotCompatible>true</IsAotCompatible>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/Polly.RateLimiting/Polly.RateLimiting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<EnableAotAnalyzer>true</EnableAotAnalyzer>
<EnableSingleFileAnalyzer>true</EnableSingleFileAnalyzer>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<IsAotCompatible>true</IsAotCompatible>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>

Expand Down
19 changes: 19 additions & 0 deletions test/Polly.AotTest/Polly.AotTest.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
<PublishAot>true</PublishAot>
<SKIP_POLLY_ANALYZERS>true</SKIP_POLLY_ANALYZERS>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Polly.Core\Polly.Core.csproj" />
<ProjectReference Include="..\..\src\Polly.Extensions\Polly.Extensions.csproj" />
<ProjectReference Include="..\..\src\Polly.RateLimiting\Polly.RateLimiting.csproj" />
</ItemGroup>
<ItemGroup>
<TrimmerRootAssembly Include="Polly.Core" />
<TrimmerRootAssembly Include="Polly.Extensions" />
<TrimmerRootAssembly Include="Polly.RateLimiting" />
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions test/Polly.AotTest/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Console.WriteLine("Hello Polly!");
Loading