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

DispatchProxy cannot work with types from collectible assemblies. #62050

Closed
teo-tsirpanis opened this issue Nov 25, 2021 · 2 comments · Fixed by #62095
Closed

DispatchProxy cannot work with types from collectible assemblies. #62050

teo-tsirpanis opened this issue Nov 25, 2021 · 2 comments · Fixed by #62095

Comments

@teo-tsirpanis
Copy link
Contributor

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.

@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Reflection untriaged New issue has not been triaged by the area owner labels Nov 25, 2021
@MichalStrehovsky
Copy link
Member

Also #30032, #60468.

Cc @vitek-karas

@vitek-karas
Copy link
Member

This is discussed in detail here: #60468 (comment)

I didn't notice the case where it simply doesn't work at all though.

@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Nov 27, 2021
@buyaa-n buyaa-n added this to the 7.0.0 milestone Nov 30, 2021
@buyaa-n buyaa-n added bug and removed untriaged New issue has not been triaged by the area owner labels Nov 30, 2021
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Dec 3, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Jan 2, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants