25

I have an application that loads external assemblies which I have no control over (similar to a plugin model where other people create and develop assemblies that are used by the main application). It loads them by creating new AppDomains for these assemblies and then when the assemblies are done being used, the main AppDomain unloads them.

Currently, it simplistically unloads these assemblies by

try
{
    AppDomain.Unload(otherAssemblyDomain);
}
catch(Exception exception)
{
    // log exception
}

However, on occasion, exceptions are thrown during the unloading process specifically CannotUnloadAppDomainException. From what I understand, this can be expected since a thread in the children AppDomains cannot be forcibly aborted due to situations where unmanaged code is still being executed or the thread is in a finally block:

When a thread calls Unload, the target domain is marked for unloading. The dedicated thread attempts to unload the domain, and all threads in the domain are aborted. If a thread does not abort, for example because it is executing unmanaged code, or because it is executing a finally block, then after a period of time a CannotUnloadAppDomainException is thrown in the thread that originally called Unload. If the thread that could not be aborted eventually ends, the target domain is not unloaded. Thus, in the .NET Framework version 2.0 domain is not guaranteed to unload, because it might not be possible to terminate executing threads.

My concern is that if the assembly is not loaded, then it could cause a memory leak. A potential solution would be to kill the main application process itself if the above exception occurs but I rather avoid this drastic action.

I was also considering repeating the unloading call for a few additional attempts. Perhaps a constrained loop like this:

try
{
    AppDomain.Unload(otherAssemblyDomain);
}
catch (CannotUnloadAppDomainException exception)
{
    // log exception
    var i = 0;
    while (i < 3)   // quit after three tries
    {
        Thread.Sleep(3000);     // wait a few secs before trying again...
        try
        {
            AppDomain.Unload(otherAssemblyDomain);
        }
        catch (Exception)
        {
            // log exception
            i++;
            continue;
        }
        break;
    }
}

Does this make sense? Should I even bother with trying to unload again? Should I just try it once and move on? Is there something else I should do? Also, is there anything that can be done from the main AppDomain to control the external assembly if threads are still running (keep in mind others are writing and running this external code)?

I'm trying understand what are best practices when managing multiple AppDomains.

tshepang
  • 12,111
  • 21
  • 91
  • 136
Ray
  • 187,153
  • 97
  • 222
  • 204
  • 5
    This is unanswerable. If you have no control over the threads in the app then there's little reason to hope that one of those threads will suddenly cooperate 3 seconds later. Isolating it in a process is the only real fix. – Hans Passant Dec 13 '10 at 19:04
  • Thanks Hans. It sounds like this is a limitation of AppDomains as compared to processes. I was hoping for a way to forcibly kill an AppDomain under any circumstance just like you can forcibly kill a process (as you suggested). – Ray Dec 13 '10 at 19:13
  • @HansPassant Does isolating execution to a dedicated process really assure this can be avoided? – lysergic-acid Sep 23 '12 at 11:27
  • See also: [No AppDomains in .NET Core! Why?](https://stackoverflow.com/q/27266907/861716). – Gert Arnold Mar 30 '23 at 08:44

3 Answers3

12

I've dealt with a similar problem in my app. Basically, you can't do anything more to force the AppDomain to go down than Unload does.

It basically calls abort of all threads that are executing code in the AppDomain, and if that code is stuck in a finalizer or unmanaged code, there isn't much that can be done.

If, based on the program in question, it's likely that the finalizer/unmanaged code will finish some later time, you can absolutely call Unload again. If not, you can either leak the domain on purpose or cycle the process.

Matthew Murdoch
  • 30,874
  • 30
  • 96
  • 127
aL3891
  • 6,205
  • 3
  • 33
  • 37
0

Try to make GC.Collect() if you do not unload the domain.

 try
    {
       AppDomain.Unload(otherAssemblyDomain);
    }
    catch (CannotUnloadAppDomainException)
    {
       GC.Collect();
       AppDomain.Unload(otherAssemblyDomain);
    }
Venom
  • 97
  • 6
0

I had similar issues with random behavior for months now, (with some app.Unload even BLOCKING forever ! on some machines) finally decided to take a big breath and made process isolation. you can spawn child console process and redirect output

if you need to cancel this is finger in the nose to kill child process and all dependencies / handles.

To an extreme i had to run dedicated cleanup code, and came to solution to create additional process with dedicated cmd line waiting input extracted from console output of initial runner process.

yes this app domain is a real joke and i think this is not a coincidence that it is not anymore in net core.

Thierry Brémard
  • 625
  • 1
  • 5
  • 14