-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
WIP API Proposal - AssemblyBuilder.DefineDynamicAssembly for AssemblyLoadContext #29842
Comments
Maybe these automatically should get their own private AssemblyLoadContext? |
Or maybe these should not be deriving from |
I am not sure how this would look like. |
This sounds good to me. |
👍 I agree we should try to modify the existing APIs in 3.0. The new API - we'll see, this is relatively small change, so maybe it would still be possible to fit it in. |
Wouldn't it be possible to provide an AssemblyLoadContext.DefineDynamicAssembly function ? The Contextual-API would require extensive locking when using thread-bound AssemblyLoadContexts, which is very common when migrating from AppDomain to AssemblyLoadContext. |
@dh1dm Adding a new API is the preferred approach. The issue was discovered too late to be included in 3.0.
Not sure why any locking would be required as |
Ok, that's true, thanks for info. |
What is the status of this issue? It's a very good proposal because we cannot define a dynamic assembly on a new Moreover, regarding to this issue dotnet/docs#10149, dynamic assemblies are never collected by the garbage collector so currently, it's not possible to create assemblies and unload them which is a huge problem... In the meantime, if this feature is not included in 3.0, is there a workaround to create assemblies and collect them? (my goal is to create types during a request lifetime) |
/cc @janvorli who might know some tricks how to unload dynamic assemblies |
Dynamic assemblies created with |
My assemblies are created with [TestClass]
public class TypeTest
{
[TestMethod]
public void CheckIfAssemblyIsCollected()
{
Type type = CreateType();
object obj = Activator.CreateInstance(type);
WeakReference weak = new WeakReference(type);
type = null;
obj = null;
Assert.IsTrue(weak.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(weak.IsAlive);
}
static Type CreateType()
{
AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("foo"), AssemblyBuilderAccess.RunAndCollect);
ModuleBuilder modb = ab.DefineDynamicModule("foo.dll");
TypeBuilder tb = modb.DefineType("Foo");
return tb.CreateType();
}
} Am I doing something wrong or is it a bug? |
I think this is probably caused by inlining. This works (prints out static void Main(string[] args)
{
var weak = T();
Console.WriteLine(weak.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine(weak.IsAlive);
}
[MethodImpl(MethodImplOptions.NoInlining)]
static WeakReference T()
{
Type type = CreateType();
object obj = Activator.CreateInstance(type);
WeakReference weak = new WeakReference(type);
return weak;
}
static Type CreateType()
{
AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("foo"), AssemblyBuilderAccess.RunAndCollect);
ModuleBuilder modb = ab.DefineDynamicModule("foo.dll");
TypeBuilder tb = modb.DefineType("Foo");
return tb.CreateType();
} |
@vitek-karas Thanks for your reply, your code works even without [TestClass]
public class TypeTest
{
[TestMethod]
public void CheckIfAssemblyIsCollected()
{
var instance = T();
instance = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(AppDomain.CurrentDomain.GetAssemblies().Any(a => a.GetName().Name == "foo"));
}
[MethodImpl(MethodImplOptions.NoInlining)]
static object T()
{
Type type = CreateType();
object obj = Activator.CreateInstance(type);
return obj;
}
static Type CreateType()
{
AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("foo"), AssemblyBuilderAccess.RunAndCollect);
ModuleBuilder modb = ab.DefineDynamicModule("foo.dll");
TypeBuilder tb = modb.DefineType("Foo");
return tb.CreateType();
}
} Here, set the instance to null is not enough, so am I obliged to always use a |
setting a variable to null doesn't mean that there is no reference to the object anymore. JIT can create helper locals with longer lifetime. If you put the lines that create the instance into a separate non-inlineable function and call that from CheckIfAssemblyIsCollected, it should work too. That would ensure that the reference to your object cannot escape to some place that you don't expect. |
Hmmm, I tried to use your solution, however, as soon as I work with the instance, assembly isn't collected anymore... [TestClass]
public class TypeTest
{
[TestMethod]
public void CheckIfAssemblyIsCollected()
{
var instance = T();
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(AppDomain.CurrentDomain.GetAssemblies().Any(a => a.GetName().Name == "foo"));
}
[MethodImpl(MethodImplOptions.NoInlining)]
static WeakReference T()
{
Type type = CreateType();
object obj = Activator.CreateInstance(type);
var expr = CreateTestExpression(type);
var test = expr.Compile().DynamicInvoke(obj); // Comment this line works
return new WeakReference(obj);
}
static LambdaExpression CreateTestExpression(Type type)
{
var funcType = typeof(Func<,>).MakeGenericType(type, typeof(bool));
var param = Expression.Parameter(type);
return Expression.Lambda(funcType, Expression.TypeIs(param, type), new ParameterExpression[] { param });
}
static Type CreateType()
{
AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("foo"), AssemblyBuilderAccess.RunAndCollect);
ModuleBuilder modb = ab.DefineDynamicModule("foo.dll");
TypeBuilder tb = modb.DefineType("Foo");
return tb.CreateTypeInfo().AsType();
}
} If yourself or someone else have another suggestion I'm listening because currently, I'm really stuck and the initial proposal seems to be the only way to achieve this... |
@Adriien-M The path you are on seems like it may eventually work. If you want to also try the unloadable AssemblyLoadContext, that should also work. See https://github.com/dotnet/corefx/issues/38426#issuecomment-500723986. Basically if the caller is running in an unloadable AssemblyLoadContext The new assembly created by You may run into similar issues as you are experiencing. |
The problem is that AppDomain.CurrentDomain.GetAssemblies keeps the assembly alive as it generates an array of assemblies. If you put the check into a separate non-inlineable function and call it, it will work (I've verified that): [MethodImpl(MethodImplOptions.NoInlining)]
public static bool CheckAssemblyExists()
{
return AppDomain.CurrentDomain.GetAssemblies().Any(a => a.GetName().Name == "foo");
} Also, you may need to call the GC.Collect(); GC.WaitForPendingFinalizers(); in a loop until the CheckAssemblyExists returns false and limit the loop count to say 10. If it doesn't succeed in those 10 iterations, it means failure. |
In fact, you will need to have the loop there, as it is clear that one iteration was not enough from the behavior of your code. |
@janvorli Thanks indeed it works if I have a loop, can I assume that Gabrage Collector will always collect my dynamic assemblies even if I don't call it explicitly? @sdmaclea I do not understand completely the strategy, can you give me a sample to try? :) |
The loop calling The |
Ok I get it, what solution seems to be the best for you? Use ALC along with separated dll or continue to create my dynamic assemblies in my application? The goal is to execute an expression on dynamic object. More precisely, expression and object structure are parsed from an XML file, so I create the type dynamically (recursively for nested types), then I build an Expression with this type as a parameter. From there, I can compile my expression and invoke it on an object that I converted previously from JSON to this dynamic type.... |
I would rely on dynamic assemblies alone - adding ALCs is just gonna complicate things - I think. If in your case the XML is static - I would probably try to precompute all of that and if it's not too big, just leave it all in memory. Creating types per-request feels very expensive. The runtime should be able to free them and so on, but it's not going to be fast. |
I agree with you, unfortunately, types can change and new types are often defined. |
Please see this PR #48072 |
I filed dotnet/dotnet-api-docs#5321 for some of the documentation issues around this (mentioned by several comments above). |
#48072 implemented the change in existing APIs. I'm leaving this issue opened to track the work to add a new API which can explicitly state the load context to use. |
@vitek-karas DefineDynamicAssembly API Extension PR #48353 |
https://docs.microsoft.com/en-us/dotnet/api/system.reflection.emit.assemblybuilder.definedynamicassembly?view=netcore-3.0
AssemblyBuilder
is implemented as a type ofRuntimeAssembly
, but there seems to be no mechanism to create it directly in a specificAssemblyLoadContext
.I assume we need at least one mechanism.
This issue is highly similar to that described in #29042
If we chose a similar solution, we would need 2 changes.
We may be able to support 1 above in 3.0 as this may be a simple PR.
It would be nice to add the new APIs for 3.1, but this may not meet the shiproom approval process
/cc @vitek-karas @jkotas @cablooshe @elinor-fung @jeffschwMSFT @sbomer
The text was updated successfully, but these errors were encountered: