-
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
Unload assembly from runtime #21707
Comments
You can't right now AFAIK. |
@gkhanna79 can you confirm that this is not expected to be supported in Core? @bartstinson what are you trying to achieve? |
@danmosemsft I need the ability to unload a dll assembly because I may have an updated version of that dll to load and I don't want to tear down the entire process to do so. I was able to do this with AppDomains before and with AppDomains gone in .NET Core there doesn't seem to be a replacement. |
@bartstinson Is that dll generated at runtime or does it have a fixed name? |
You can load updated copy of the .dll in a new assembly load context (https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/assemblyloadcontext.md), and keep the old copy loaded. Of course, this only works if you do not keep doing it again and again.
Unloading is not supported in .NET Core today. https://github.com/dotnet/coreclr/issues/552 is the proposed plan to add it. |
@davidfowl dll has fixed name. @jkotas Thanks for the link. Are there any sample code or guides for how to use this LoadContext? |
If you just need to load a single assembly, a good sample is implementation of https://github.com/dotnet/coreclr/blob/b38113c80d04c39890207d149bf0359a86711d62/src/mscorlib/src/System/Runtime/Loader/AssemblyLoadContext.cs#L471 The basic structure is: internal class MyAssemblyLoadContext : AssemblyLoadContext
{
internal MyAssemblyLoadContext()
{
}
protected override Assembly Load(AssemblyName assemblyName)
{
return null;
}
}
...
AssemblyLoadContext alc = new MyAssemblyLoadContext();
result = alc.LoadFromAssemblyPath(path);
... The tests https://github.com/dotnet/corefx/blob/master/src/System.Runtime.Loader/tests/AssemblyLoadContextTest.cs have more advanced examples. |
@jkotas Thanks. That helps a lot. Will monitor the threads for unload functionality |
AssemblyLoadContext is convenient and fast. However, the inability to unload a context causes enormous problems for plug-in scenarios. Orphaned contexts reside in memory until the process is dumped. It's disappointing this didn't make it for 2.0. Is there an issue somewhere that is being tracked (or is THIS the issue)? @terrajobst any ideas on progress here? |
It is very important to be able to dynamically load and unload assemblies. I have a situation where I dynamically load one or more of MANY (as in >10^2) assemblies for specialized processing of selected data. Once the data has been processed, those assemblies are no longer needed (until some relatively distant future time) and can and should be unloaded. Loading all of those assemblies in memory at once would be impractical. Merging them is also impractical (and architecturally unwise.) I would very much like to see Microsoft add this functionality back. It was there before for a reason. It is still needed. This is a perfect example of how when an organization attempts to rewrite an existing piece of software they often leave out functionality because they were unable to understand the initial reason it was added to begin with. |
I mean... it might be better than rewriting the functionality without understanding the initial reason it was added to begin with. 😜 |
Is there any update on this functionality? Is there yet any way to unload an assembly in .NET Core? Is this functionality even planned? Why is it actually becoming easier to actually work in C++ than C#? Seriously. Does anyone else notice the irony? |
Don't look very promising, considering this comment: dotnet/coreclr#8677 (comment) |
This is a must-have feature when dotnet core should be a modern, useful and competitive language! |
Why is this closed anyway? |
In the other thread MS has made it clear that this is planned for .NET Core 3.0. |
This is in the .NET Core 3.0 nightly builds now. Please give it a try and give us feedback. https://github.com/dotnet/corefx/blob/master/Documentation/project-docs/dogfooding.md |
@jkotas Runnning |
@janvorli Could you please share an example of what works in the current builds? |
Here is a simple example that loads, executes and unloads a simple "hello world" program in a loop: using System;
using System.Reflection;
using System.Runtime.Loader;
class SimpleUnloadableAssemblyLoadContext : AssemblyLoadContext
{
public SimpleUnloadableAssemblyLoadContext()
: base(isCollectible: true)
{
}
protected override Assembly Load(AssemblyName assemblyName) => null;
}
class Program
{
private static int ExecuteAssembly(Assembly assembly, string[] args)
{
MethodInfo entry = assembly.EntryPoint;
object result = entry.GetParameters().Length > 0 ?
entry.Invoke(null, new object[] { args }) :
entry.Invoke(null, null);
return (result != null) ? (int)result : 0;
}
static void Main(string[] args)
{
for (;;) {
var context = new SimpleUnloadableAssemblyLoadContext();
Assembly assembly = context.LoadFromAssemblyPath(@"D:\repro\hello\bin\Debug\netcoreapp3.0\hello.dll");
ExecuteAssembly(assembly, Array.Empty<string>());
context.Unload();
}
}
} |
Works very well! No increase in memory usage at all. Well done 👍 Tested on |
Thank you for the info. However, after unloading, the DLL could not be deleted from the directory? Have I possibly ignored something? Thanks in advance. Code:
} class Program
} |
@FrankDoersam calling "Unload" just initiates the unloading. The actual collection happens after one or more (depending on whether your code that's loaded into the assembly load context uses finalizers). Also, you have to make sure there are no GC references to your SimpleUnloadableAssemblyLoadContext or anything that lives inside of that context. In your code, the
To be sure that the assembly load context was unloaded, you can return WeakReference containing the AssemblyLoadContext from the non-inlineable function and loop the |
Thank you for the helpful tip. Could you please show a short example code that implements the function. (Unfortunately there is very little currently available on the topic, I think the example would be very helpful for many :)) Thanks in advance. |
@FrankDoersam here is your test modified as per my tips: class SimpleUnloadableAssemblyLoadContext : AssemblyLoadContext
{
public SimpleUnloadableAssemblyLoadContext()
: base(isCollectible: true)
{
}
protected override Assembly Load(AssemblyName assemblyName) => null;
}
class Program
{
private static int ExecuteAssembly(Assembly assembly, string[] args)
{
MethodInfo entry = assembly.EntryPoint;
object result = entry.GetParameters().Length > 0 ?
entry.Invoke(null, new object[] { args }) :
entry.Invoke(null, null);
return (result != null) ? (int)result : 0;
}
[MethodImpl(MethodImplOptions.NoInlining)]
static WeakReference LoadInContextAndUnload()
{
AssemblyLoadContext tt = new SimpleUnloadableAssemblyLoadContext();
Assembly assembly = tt.LoadFromAssemblyPath(@"C:\Lokale Daten\AppDomainUnload\ConsoleApp1\bin\Debug\netcoreapp3.0\Plugin\Plugin1.dll");
ExecuteAssembly(assembly, new string[] { "arg1", "arg2"});
tt.Unload();
return new WeakReference(tt)
}
static void Main(string[] args)
{
WeakReference ttWeakRef = LoadInContextAndUnload();
for (int i = 0; i < 8 && ttWeakRef.IsAlive; i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
if (ttWeakRef.IsAlive)
{
Console.WriteLine("Unload failed");
}
Console.ReadKey();
}
private static void Context_Unloading(AssemblyLoadContext obj)
{
Console.WriteLine("Unloading");
}
} |
Might be an idea to hide this complexity before going stable. Or I guess you'll risk seeing quite some issues now and then on your trackers. Just saying. 😎 WTBS, thanks for the sample. |
Would be nice if you could extend the function Unload to this? :) |
I agree it would be nice if .Unload were to unload immediately as it does with AppDomain.Unload. Though if this means .Unload would have to be changed to invoke a full GC internally it would perhaps be better as an optional thing. |
|
Any differences between Windows and MacOS (for example)? Maybe this works without issues because of the simple nature of the assembly I load?
|
It works without issue because of you are creating a copy of the whole assembly upfront. It avoids locking the files on disk, but you pay performance penalty for it. |
@jkotas I understand that, I was actually just testing to delete the assembly that was unloaded since @FrankDoersam had issues with deleting the assembly after unloading. Edit: I had no issues deleting before I did the copy thing either Learning a lot of tricks in this thread :) Will be of great use to us when 3.0 is released |
I have a few questions:
|
We do not plan to have built-in support for shadow copying in the runtime. You can implement it yourself using AssemblyLoadContext with any policy you like (where to copy, how much to copy, how to copy it, when to delete, ...). .NET Framework had one hard-coded policy for shadow copying and we always got an endless stream of request how to make it more configurable. Alternatively, you can avoid locking files in memory by loading the assembly bits into memory first, and then hand that memory to AssemblyLoadContext.
We plan to have better tracing for assembly loader in general. Yes, these are poor APIs that should not be used by well-written programs (even if you do not create self contained contexts). |
One nice feature of shadow copying is that it sets up Also things like subscribed global events in non-collectible assemblies can keep a context alive. Is there any easy way of finding all objects which are keeping the context alive? |
We would like to be able to enable linking of .NET Core applications into single file (https://github.com/dotnet/coreclr/issues/20287). Code that depends on properties like this always breaks "single file". I do not think we would want to be adding more APIs that make it easier for more code to depend on physical locations of .dlls on disk.
It is no different from finding all object which are keeping other objects alive: how to find memory leak in C#. |
@jkotas makes sense, thanks for the help. If anybody is interested I wrote something which lets me create a collectible |
I am not sure if there is a safe way to build a notification mechanism using finalizers. AFAIK you should never access managed references in a finalizer because they may have already been finalized. So all I can do is polling I guess. Also WeakReferences may be expensive since the allocate a GC Handle. We've been bitten my memory leaks caused by WeakReferences and their GC handles. |
If whatever component you are calling into is designed properly you should get an ObjectDisposedException if that is the case which you can just choose to swallow. But ofc. if you actually want a callback then it is your responsibility to keep that object alive some other way independently of the AssemblyLoadContext. And just to be clear here, collected != finalized. Every object accessible from the object which finalizer you are in must still be live even if they may have been finalized and are currently marked for collection - you can also still resurrect them by making them rooted again. You are not touching freed memory, they are still valid .NET objects. |
What about docs ? Any plans to desribe a usage of unlodable assemblies ? I guesse its gonna killer feature when released! Because net core gona be more modular ! And it gonna be easy to extend functionality of software ! Cms! All cases when modularity is aproved ! |
@Diaskhan I am planning to write doc on that with examples, hints etc. so that people can successfully use this feature. |
Hey, just a small question if you dont mind - I see all the issues in Unloadability project |
It is on track for .NET Core 3.0. It has been mentioned in the official .NET Core 3.0 Preview 2 blog post: https://blogs.msdn.microsoft.com/dotnet/2019/01/29/announcing-net-core-3-preview-2/ |
Any tips on reducing memory usage? Right now one |
@TETYYS 8MB for one https://github.com/dotnet/samples/tree/master/core/tutorials/Unloading has a sample of a simple unloadable plugin. It costs less than 100kB on average to load the plugin if the sample is modified to load the plugin a loop. cc @janvorli |
Thanks, I'm not sure what exactly I did worked, but I think fiddling with dependencies of assembly did it. Memory is no longer an issue even in limited environment with more than 1000 assemblies. Thanks again for your hard work. |
If you can figure out what your were doing wrong, it might be useful to share here as an antipattern for others to see. |
How about unhandled exceptions in plugin? it is real catch it on host level? (legacy .net allows it in a buggy way |
@vasilijgla yes, it works just fine. Both try / catch around the call to a plugin method and AppDomain.UnhandledException work. |
Hello,
Now that AppDomains have been removed from .NET Core, how does one unload an assembly from memory once it has been loaded? AssemblyLoadContext has an Unloading event but no way to programmatically trigger the unload of an assembly
The text was updated successfully, but these errors were encountered: