Closed
Description
Description
The System.Reflection.DispatchProxy
type creates its types in an AssemblyBuilder
created with AssemblyBuilderAccess.Run
, leading to not just a resource leak when DispatchProxies
with collectible types are used, but a total inability, because "[a] non-collectible assembly may not reference a collectible assembly".
Reproduction Steps
I wrote the following demo that tests the unloadability of an ALC:
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
public class SimpleProxy<T> : DispatchProxy where T: class
{
public T Target { get; private set; }
public static T Decorate(T target = null)
{
var proxy = (SimpleProxy<T>) (object) Create<T, SimpleProxy<T>>();
proxy.Target = target ?? Activator.CreateInstance<T>();
return proxy as T;
}
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
var result = targetMethod.Invoke(Target, args);
return result;
}
}
public interface ITestObject { }
public class TestObject : ITestObject
{
public static ITestObject Create() => new TestObject();
public static ITestObject CreateDecorated() =>
SimpleProxy<ITestObject>.Decorate(Create());
}
static class Program
{
[MethodImpl(MethodImplOptions.NoInlining)]
static WeakReference CreateObjectInUnloadableALC(bool createDecorated)
{
var alc = new AssemblyLoadContext(null, true);
var asm = alc.LoadFromAssemblyPath(typeof(Program).Assembly.Location);
var typ = asm.GetType(nameof(TestObject));
var methodName = createDecorated ? nameof(TestObject.CreateDecorated) : nameof(TestObject.Create);
var method = typ.GetMethod(methodName);
method.Invoke(null, null);
return new WeakReference(alc);
}
static void CollectUntilWeakReferenceDies(WeakReference wr)
{
int i;
for (i = 0; i < 10 && wr.IsAlive; i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
if (wr.IsAlive)
Console.WriteLine("Still alive");
else
Console.WriteLine($"Collected after {i} GCs.");
}
static void Main()
{
Console.WriteLine("Testing with a raw object");
var wr = CreateObjectInUnloadableALC(false);
CollectUntilWeakReferenceDies(wr);
Console.WriteLine("Testing with an object wrapped by DispatchProxy");
wr = CreateObjectInUnloadableALC(true);
CollectUntilWeakReferenceDies(wr);
}
}
Expected behavior
Testing with a raw object
Collected after 1 GCs.
Testing with an object wrapped by DispatchProxy
Collected after 1 GCs.
Actual behavior
Testing with a raw object
Collected after 1 GCs.
Testing with an object wrapped by DispatchProxy
Unhandled exception. System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
---> System.NotSupportedException: A non-collectible assembly may not reference a collectible assembly.
at System.Reflection.Emit.ModuleBuilder.GetTypeRef(QCallModule module, String strFullName, QCallModule refedModule, String strRefedModuleFileName, Int32 tkResolution)
at System.Reflection.Emit.ModuleBuilder.GetTypeRefNested(Type type, Module refedModule, String strRefedModuleFileName)
at System.Reflection.Emit.ModuleBuilder.GetTypeTokenWorkerNoLock(Type type, Boolean getGenericDefinition)
at System.Reflection.Emit.ModuleBuilder.GetTypeTokenInternal(Type type, Boolean getGenericDefinition)
at System.Reflection.Emit.SignatureHelper.AddOneArgTypeHelperWorker(Type clsArgument, Boolean lastWasGenericInst)
at System.Reflection.Emit.SignatureHelper.AddOneArgTypeHelperWorker(Type clsArgument, Boolean lastWasGenericInst)
at System.Reflection.Emit.SignatureHelper.GetTypeSigToken(Module module, Type type)
at System.Reflection.Emit.ModuleBuilder.GetTypeTokenWorkerNoLock(Type type, Boolean getGenericDefinition)
at System.Reflection.Emit.ModuleBuilder.GetTypeTokenInternal(Type type, Boolean getGenericDefinition)
at System.Reflection.Emit.TypeBuilder..ctor(String fullname, TypeAttributes attr, Type parent, Type[] interfaces, ModuleBuilder module, PackingSize iPackingSize, Int32 iTypeSize, TypeBuilder enclosingType)
at System.Reflection.Emit.ModuleBuilder.DefineType(String name, TypeAttributes attr, Type parent)
at System.Reflection.DispatchProxyGenerator.ProxyAssembly.CreateProxy(String name, Type proxyBaseType)
at System.Reflection.DispatchProxyGenerator.GenerateProxyType(Type baseType, Type interfaceType)
at System.Reflection.DispatchProxyGenerator.GetProxyType(Type baseType, Type interfaceType)
at System.Reflection.DispatchProxyGenerator.CreateProxyInstance(Type baseType, Type interfaceType)
at System.Reflection.DispatchProxy.Create[T,TProxy]()
at SimpleProxy`1.Decorate(T target) in Program.cs:line 12
at TestObject.CreateDecorated() in Program.cs:line 33
--- End of inner exception stack trace ---
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
at Program.CreateObjectInCollectibleALC(Boolean createDecorated) in Program.cs:line 46
at Program.Main() in Program.cs:line 70
Regression?
No
Known Workarounds
Use non-collectible ALCs.
Configuration
Tested in .NET 5, the relevant code did not change in 6.
Other information
The logic around creating AssemblyBuilders
needs to change; I will try to do it in the future.