Why isn’t there an Assembly.Unload method?



We frequently get asked why you cannot unload an assembly from an app domain.  After all, you can do a FreeLibrary() in unmanaged code to unload a DLL, and the CLR is just using DLL’s right?


Reasons to Want to Unload


There are generally two reasons that you want to unload an assembly:  space and versioning.  The former is obvious, you want to free up allocated memory in the process.  The second refers to wanting to load a newer version of an assembly, as for example Asp.Net does when you compile a new version of your application.  If the assembly’s underlying file is locked in the file system, you cannot replace it.


Why Not Support It?


There are a few problems with unloading an individual assembly, some of them hard design issues and some of them just work:


1.  First off, you are running that code in the app domain (duh!).  That means there are potentially call sites and call stacks with addresses in them that are expecting to keep working.  Have you ever gotten an access violation where your EIP points to 0x????????  That is an example where someone freed up a DLL, the pages got unmapped by the memory system, and then you tried to branch to it.  This typically happens in COM when you have a ref counting error and you make an interface method call.  We cannot afford to be as lose with managed code.  We must guarantee we know all of the code you are executing and that it is type safe and verifiable.  That means explicit tracking of anything that could be using that code, including GC objects and COM interop wrappers.  This tracking is handled today around an app domain boundary.  Tracking it at the assembly level becomes quite expensive.


2.  Say you did manage to track all handles and references to already running code by an assembly.  Assuming you didn’t ngen the code, once you successfully freed up the assembly, you have only freed up the metadata and IL.  The JIT’d code is still allocated in the app domain loader heap (JIT’d methods are allocated sequentially in a buffer in the order in which they are called).  Now we do know the identity of all methods, so we could go back and track down all of the code and turn the heap into a malloc style heap with a free list (in fact we had a code pitching jitter we prototyped way back when on Windows CE that did precisely this to limit the overall JIT heap size).  So this one is solvable and just falls into the work column with a small hit on allocation and jitted method locality (probably not enough to measure).


3.  The final issue relates to code which has been loaded shared, otherwise more formally know as “domain neutral” (check out /shared on the ngen tool).  In this mode, the code for an assembly is generated to be executed from any app domain (nothing hard wired).  This has some interesting trade offs:  on the one hand you can execute the same code in any app domain so it loads faster in subsequent app domain instances (its already there).  On the other hand, the code must therefore be tracked in all app domains it has ever been loaded into before it can be freed.  With the current v1.0 and v1.1 product it is not even possible to unload domain neutral code because of these restrictions.


In the end you should be getting the flavor that this is not an impossible feature to implement, but is also non-trivial.  In essence, we have already built in the design around the notion of an app domain and amortized it across all assemblies, hence the model.  I will not rule out having such a feature in the future.  But it is not planned for the Whidbey product at this time.


So What’s the Alternative?


It is recommended that you design your application around the application domain boundary naturally, where unload is fully supported.  For example, Asp.Net hosts applications in an app domain and unloads them when they are no longer needed or out of date.  SQL Server Yukon does the same thing for SQL/CLR.  In addition, you can use the Shadow Copy feature of Fusion to avoid the file in use locking error (also used by Asp.Net).  The added advantage of this model is that you can write your host to monitor and abort app domains which engage in bad behavior (such as using too much memory or resources).  This can be very helpful when you are hosting potentially un-trusted or potentially less robust code. 


If there is interest, I have been considering posting a hosting sample which does app domain recycling policy as this does come up quite a bit…

Comments (60)

  1. Francis says:

    Jason, to be honest this SUCKS BIG TIME. Whatever the technical difficulties are you should try and get them resolved. We are not going turn our app into a big mess and debug app domain leaks (to anyone planning to architect apps around app domains: don’t even think about it). Instead we decided to cut the add-in feature we wanted to do and we are telling our users that this is not possible with the .NET Framework.

  2. Francis says:

    Also, thanks a lot for explaining this. It helps a lot to know this won’t be repaired anytime soon.

  3. zia says:

    I agree this is one of the biggest flaws in .NET design. We also chose not to use .NET for a project because of this.

  4. mirobin says:

    If you really want to take the overhead hit of being able to load/unload single assemblies at a time, just write a simple wrapper class which manages the assembly — loading, getting a reference to the assembly, and unloading it. The wrapper class would be responsible for creating the AppDomain on the fly and destroying it when you wanted to ‘unload’ the assembly.

    This isn’t hard stuff folks.

    Managing assemblies you want to unload in AppDomains actually makes things easier in some respects, depending on your application model.

  5. I can see a lot of good passion around this one <g>

    Francis – I’d like to understand your concerns more. In particular for an add-in model, the app domain model should be ideal. For example, if I were going to host managed code in Outlook, I’d want to do it in an app domain where I could also lower the CAS permissions so that, for example, I could prevent add-in code from touching my address book and spawning a Melissa virus. What is it about app domains that bugs you? If we either gave a really great sample or even built some hosting into the CLR (think thread pool for app domains) would that help alleviate concerns?

    As mirobin suggests, you can abstract pieces of this too. the one piece of the programming model that does get exposed in this case is inter-app domain remoting. So depending on how you want to program against your objects you may need to inherit from MarshalByRef.

  6. David Levine says:

    Jason,

    I use multiple appdomains all the time and I think it has worked out fairly well, but there are some issues and limitations. Hosting plugins in separate appdomains becomes difficult is there is extensive communication between the host and the plugin due to the remoting boundary. I’m also told that some limitations wont be resolved until Whidbey, such as the transitive loading of assemblies using custom evidence (e.g. different security zone), but right now it is a security hole.

    Right now I am trying to figure out how to use different binding redirects for each appdomain using app config files. Can this be done in v1.1? In other words, is there a way to have different appdomains use a different binding redirect policy for the same assembly?

  7. Kerry Jenkins says:

    I am writing a windows forms application that will house multiple MDI children applications each in their own assembly. These MDI children are loaded dynamically. I would like to be able to unload an assembly so that I can bring in a new version without having to take down the whole parent application. I have attempted to put the MDI child in a separate appdomain but this is not possible due to WinForms serialzation problems. An Assembly.Unload would be very helpful to me.

  8. In my attempts to get CodeSmith templates to execute in another AppDomain, I basically ran into a brick wall because it was either impossible or just a TON or work to get an object in another AppDomain to be the selected object in a PropertyGrid instance. If anyone knows of a way to do this, please contact me and let me know. Here is a link to more information about the problem I am having:

    http://weblogs.asp.net/ericjsmith/archive/2004/05/06/127185.aspx

  9. Jazon Zander skriver om, hvorfor der
    ikke findes en Assembly.Unload metode.
    Vi lavede nemlig en arkitektur…

  10. The thing that absolutely sucks is that cross-appdomain calls are uselessly slow. See the results I posted at http://www.jelovic.com/weblog/e87.htm to see how bad the situation is.

    Any plans to fix that?

  11. Keith says:

    I would love to see an application sample.

    thanks

  12. Another "workaround" is Light Weight Codegen in Whidbey. Hopefully, in the future Light Weight Codegen will also support creating types, that would really be nice work around.

    Using AppDomains is not an option for perf critical stuff.

  13. Lot’s of good comments in here. A few things:

    The app domain boundary does enforce a programming model since you are remoting objects between app domains. The mechanics are quite straight forward: you inherit from MarshalByRef and use System.Runtime.Remoting to instantiate your new object. However, around any general remoting boundary (in which I include x-app domain, p/invoke & COM interop, as well as x-process), you want to be cogniscent of versioning and performance. There are a few general classes of problems described in the comments that demonstrate these issues:

    1. Performance. The first solution starts with the design. You want to use interface boundaries which, as Chris Brumme likes to call them, are ‘chunky’ and not ‘chatty’. For example, if you are going to loop 1 million times around a function call, don’t make the function call a marshalling boundary. Move the loop to the other side of the equasion.

    The next thing is when you have to do mashalling, can it be as fast as possible. The good news is that in Whidbey, the x-app domain remoting code is up to several times faster than in v1.0 and v1.1, primarily for intrinsic types. When you marshal such calls, serialization is now skipped and the data is directly copied from one stack to another.

    2. Square peg, round hole. If the place you are trying to host a plug-in doesn’t support MarshalByRef semantics, then I can appreciate how it becomes much more difficult to use this solution. You basically have to separate out the logic for the plug-in from the pieces that need to operate on non-marshallable objects. So if your MDI object has some unique logic you can put that into another app domain, but customizing the UI which requires touching Winforms objects has to be done in the app domain in which they live.

    3. Versioning. Not yet mentioned but important is making sure versioning is enforced between domains. If you are hosting 3rd party plug-ins, getting this right is important (and probably a good topic for another blog). This is one thing I think the Indigo guys learned from our experience and improved upon.

    Around the security stuff mentioned, I’m checking on that and will post back.

  14. Check out my post regarding shadow copying and dynamic load of assemblies. I will gladly share samples of code if anyone is interested. Indeed, the problem of AppDomain boundary manifests itself differently in two distinct usage scenario: UI plug-in and server plug-in. Since UI plug-in usually (by definition?) interacts by the UI cross-domain marshaling will be expensive. However, in case where you have a server process (such as ASP.NET hosting process) that executes "work items" as they come up, loading parts or the whole execution engine into a separate AppDomain works just fine. I have successfully implemented this solution in conjunction with Reactor pattern to create a generic server process that can host a multitude of process engines. The only problem I still face is "knowing" when to unload the old AppDomain to free up space without burdening the hosting process with notifying the engine when to unload.

  15. dbj says:

    Iam sorry but I see why lack of Assembly.Unload() method,makes (UI)Plugins or "Server Plugins" very difficult or impossible to write using .NET ?

  16. dbj says:

    QUICK RETYPE

    Iam sorry but I DO NOT see why lack of Assembly.Unload() method,makes (UI)Plugins or "Server Plugins" very difficult or impossible to write using .NET ?

  17. Paulb says:

    Very interested in the hosting sample and app domain recycling

  18. Thong Nguyen says:

    Well Java manages to unload Java classes, code & meta data just fine (it occurs after the last reference to the class & classloader dissapears). All the problems you talk about are solved by using this fancy thing called a Garbage Collector which I hear is already included in .NET.

  19. David Levine says:

    Lacking Assembly.Unload makes some aspects of plugins a trifle more difficult, but even if you had it there are numerous other issues related to versioning that having an Unload function would not help you with. You might think that all you need to do is unload the old plugin and load the new one but there are many more issues then this. Here are a few of them.

    1, whether to define an interface that plugins implement or derive all plugins from an abstract base class. Interfaces don’t version well – once published they must live forever unchanged or break all plugins using them. Classes are more flexible but .net only supports single inheritance. I think that using a common base class with reasonable stubbed implementations of methods added/modified after the initial release makes the host code simpler.

    2, whether to sign assemblies. This greatly impacts how the runtime handles versioning. We use strong names on all our assemblies – this means the runtime uses the assembly’s fullname for binding purposes. This can also affect deployment.

    3, how to deploy. It’s easiest to use xcopy deployment but then plugins may have issues resolving references if shared assemblies are not located in the GAC. One source of trouble are common assemblies that define types to be shared amongst various components, e.g. the Load vs. LoadFrom context, and assemblies that use different versions of the same assembly. It is critical that all components refer to the same assembly when types are shared, but if the plugins are compiled and built against a version other then the one deployed (common where 3rd parties develop the plugins) then binding redirects must be used. This would be easier to solve if the runtime supported a mechanism for programmatically doing binding redirects when loading types.

    4, when crossing appdomain boundaries all shared types must be defined from the same assembly – related to item 3. Also, it is a remoted interface, with all of those issues to deal with.

    This gets complicated pretty quickly. Simply unloading one plugin and loading another does not solve these.

  20. Eric says:

    Please make Assembly.Unload work and please make it work that my add-ins don’t crash whenever we change the Assembly or Framework version numbers.

  21. Dave says:

    It seems like a lot of the comments revolve around a scenario using "plugins". I’ve tried to go the AppDomain loading/unloading route to implement such plugins, but it was just too much of a hassle – I ended up with a "load plugins on startup" solution – which is kind of a hassle for a Web Application which needs to be live most of the day. Has anyone had any success creating/destroying AppDomains from within a Web Application?

    It would be nice, since this is such a popular scenario, if there was a reference example of how to add solid plugin support to an application (basically your AppDomain loading/recycling example).

  22. vrubins says:

    I tried to use separate AppDomain to load a managed dll that actually compiles a code on-the-fly to in-memory assembly and runs it. I then unload my AppDomain but memory was not released – the in-memory assembly seems to be loaded into a default domain as well.

    I need to do a lot of communications with the in-memory assembly and I need to re-compile it thousands of times. But when I am done with it – I know for sure there are no references and handles – I just want it gone.

    Despite all the good reasons noted here – I too think that this feature is a valuable one and wish it was implemented.

    There are million other ways to crash .NET app, why be afraid of this one.

  23. Carol says:

    Fix it now. If MS can’t fix it maybe http://www.go-mono.com can. I wouldn’t mind running Mono instead of .NET if this makes my add-ins work.

  24. Sean Hernandez says:

    This would be a really cool Rotor research topic.

  25. Sigurthr says:

    Samples! Yes, yes, yes! I have a major project in the framework and I just ran into this wall. I cannot and do not want to recode on a different environment as .net has made so many improvements if there is a good work around/solution I’ll use it. I am alread using a interface based modle and have been keeping them in a separate asembly as I am loading to classes of dynamic plug-ins that have to interop. One class scripts against the other with the framework providing the fundamental services.

    Thanks,

    Sigurthr

  26. juju says:

    I am using a script system in c#. When the user changes and compiles his new "versioned" class, tadaaaa! "Compiler error. your file is in use!!!". This is due to the f lack os Assmble.Unload method…..

  27. musti says:

    hi,

    is there any chance or aspiration, that the Assembly.Unload method will be implemented in the new .Net version (v2.0)???

    Thanks,

    musti

  28. musti says:

    is there anyone from microsoft, who can answer my question from 7/6/2004 4:16AM????

    …or is it true that microsoft don`t think about it (Assembly.Unload)???

    I have tried to solve my problem with AppDomain`s, but it is realy not a good way…(and it doesn`t solve my problem at the end )

    musti

  29. Jason Zander says:

    Hi Musti – sorry for the delay; I’ve been on vacation. We do not have plans to implement an Unload method in V2.0. We are basically code complete with the release and down to stress testing and fit and finish style fixes. Doing Unload would be a pretty pervasive feature to implement which would touch too much of the runtime for this release.

    I have some folks working on samples as we speak; stay tuned…

    Jason

  30. I’m running into some differences between instantiating a component in another AppDomain via COM as opposed to instantiating the same in a WinForm application. COM basically gets a corrupted or invalid instance of the remote object while everything works fine iwth the same code in WinForms.

  31. Well we ended up writing a VS.Net plugin to add to our developer testing tools (something like NUnitPlugin,…

  32. What is an Application Domain (AD)?

    An Application Domain is a CLR feature that provides a unit of isolation…

  33. B# .NET Blog says:

    Introduction

    The last couple of days I’ve been playing around with some stuff around WCF (formerly known…

  34. There’s no way to unload an individual assembly without unloading all of the appdomains containing it.

  35. 林西 says:

    微软装配车的大门似乎只为货物装载敞开大门,却将卸载工人拒之门外。车门的钥匙只有一把,若要获得还需要你费一些心思。我在学习Remoting的时候,就遇到一个扰人的问题,就是Remoting为远程对象仅提…

  36. I&#39;ve come to a case, where, in my application, i need to load the assembly dynamically. Its not a

  37. What is an Application Domain (AD)? An Application Domain is a CLR feature that provides a unit of isolation

  38. Colby Africa says:

    Sometimes you need to load an assembly for use or inspection.&#160; The problem is that loading an assembly

  39. Abhinaba’s blog What’s new in Windows Mobile 6.1 and 6.5 around memory Dynamic assembly unloading Again