YarpOutputCachePolicyProvider breaks AOT compilation in 2.2.0
Describe the bug
I just updated to 2.2.0 and while validating my proxy I get an exception during startup caused by YarpOutputCachePolicyProvider, probably because it is using reflection and it's trying access code that is trimmed away. I'm not even using output caching.
Here's the relevant part of the stack trace:
Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.
at System.Reflection.DynamicInvokeInfo..ctor(MethodBase, IntPtr) + 0x314
at Internal.Reflection.Execution.ExecutionEnvironmentImplementation.TryGetMethodInvokeInfo(RuntimeTypeHandle, QMethodDefinition, RuntimeTypeHandle[], MethodBase, MethodSignatureComparer&, CanonicalFormKind) + 0x191
at Internal.Reflection.Execution.ExecutionEnvironmentImplementation.TryGetMethodInvoker(RuntimeTypeHandle, QMethodDefinition, RuntimeTypeHandle[]) + 0xe3
at Internal.Reflection.Core.Execution.ExecutionEnvironment.GetMethodInvoker(RuntimeTypeInfo, QMethodDefinition, RuntimeTypeInfo[], MemberInfo, Exception&) + 0x11e
at System.Reflection.Runtime.MethodInfos.NativeFormat.NativeFormatMethodCommon.GetUncachedMethodInvoker(RuntimeTypeInfo[], MemberInfo, Exception&) + 0x50
at System.Reflection.Runtime.MethodInfos.RuntimeNamedMethodInfo`1.GetUncachedMethodInvoker(RuntimeTypeInfo[], MemberInfo) + 0x1b
at System.Reflection.Runtime.PropertyInfos.RuntimePropertyInfo.GetValue(Object, BindingFlags, Binder, Object[], CultureInfo) + 0x54
at System.Reflection.PropertyInfo.GetValue(Object, Object[]) + 0x1d
at Yarp.ReverseProxy.Configuration.YarpOutputCachePolicyProvider..ctor(IOptions`1 outputCacheOptions) + 0xa7
Here is what I think the offending code is: https://github.com/microsoft/reverse-proxy/blob/99ce21a64e2115f9887e99025ebd2cc2a098d656/src/ReverseProxy/Configuration/IYarpOutputCachePolicyProvider.cs#L35-L37
To Reproduce
Publish using PublishAot and attempt to run the application.
Potential fix
You might be able to use the UnsafeAccessorAttribute in .NET 8:
// Define an unsafe accessor
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_NamedPolicies")]
private static extern Dictionary<string, IOutputCachePolicy>? GetNamedPolicies(OutputCacheOptions c);
// Then use it like this
_policyMap = GetNamedPolicies(_outputCacheOptions);
For older versions of .NET you need to make sure that the code you are trying to use isn't being trimmed, one way I've successfully done this in my own projects is to create an unused field that references the type with the DynamicallyAccessedMembersAttribute stating what I need to keep.
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
private static readonly Type keepOutputCacheOptions = typeof(OutputCacheOptions);
Further technical details
- Version 2.2.0
- Windows
I tried to get my potential fixes above to work but had no luck, I think more code is being trimmed than I first suspected.
However, I was able to figure out that adding the following line is enough to make the compiler keep the necessary code:
new OutputCacheOptions().AddPolicy("AOT workaround", (IOutputCachePolicy)null!);
Thanks for raising the issue. This looks like a Native AOT bug and appears to be fixed in .NET 9. I think we should add a workaround in YARP to unblock .NET 8 though.
I can confirm that there is no need for a workaround in .NET 9, I updated my proxy yesterday and it's working perfectly.