Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the scope of finalizer thread - per application domain or per process?

Based on all my reading there should be one GC thread to invoke all finalizers. Now, the question is what is the scope of this "one" thread - per process or per application domain, as the whole intention of domains is to separate and make "independent" different applications in one process space.

I read here:

If an unhandled exception occurs in a finalizer the CLR's executing thread will swallow the exception, treat the finalizer as if it completed normally, remove it from the freachable queue and move onto the next entry.

More serious though, is what happens if your finalizer doesn't exit for some reason, for example it blocks, waiting for a condition that never occurs. In this case the finalizer thread will be hung, so no more finalizable objects will be garbage collected. You should be very much aware of this situation and stick to writing the simplest code to free your unmanaged resources in finalizers.

Another consideration is what happens during application shutdown. When the program shuts, the garbage collector will endeavour to call the finalizers of all finalizable objects, but with certain limitations:

  • Finalizable objects are not promoted to higher heap generations during shutdown.

  • Any individual finalizer will have a maximum of 2 seconds to execute; if it takes longer it will be killed off.

  • There is a maximum of 40 seconds for all finalizers to be executed; if any finalizers are still executing, or pending at this point the whole process is abruptly killed off.

Too many posts (and even official documentation) misuse of the terms "application", "process" and "application domain" - most of them even assuming that they are equal, because usually applications are run in a single application domain in a single process. This misuse makes all of these docs hard to read, and not even useful.

So, my question assumes more than one applications, each run in a separate application domain in a single process.

Does all these application share the same GC and finalizer threads? Does the problem described in the article above (hang finalizer thread) will affect all applications in that process? If yes - is there a workaround (besides to not use bad applications), like somehow to discover the finalizer thread and send it Thread.Abort?

All above is because I hit similar problem. My application runs in a separate application domain as addin to third party software (Outlook). Because of various reasons I need to call GC.Collect and GC.WaitForPendingFinalizers to fully release COM references (the usual interop routines are not enough for Office/Outlook), when a particular other third party addin is running, my GC.WaitForPendingFinalizers hangs forever, so I suspect that there is a "bad" finalizer in that third party adding. I have no control over replacing/removing that adding (client's requirement), so I have to figure out on my own how to make them co-exist.

like image 796
Sunny Milenov Avatar asked Oct 27 '08 22:10

Sunny Milenov


1 Answers

Looks like it is indeed just one thread per CLR instance within the process - at the moment, anyway. Here's some code to show that:

Test.cs:

using System;

class Test
{
    static void Main()
    {
        AppDomain.CreateDomain("First")
                 .ExecuteAssembly("ShowFinalizerThread.exe");
        AppDomain.CreateDomain("Second")
                 .ExecuteAssembly("ShowFinalizerThread.exe");
    }
}

ShowFinalizerThread.cs:

using System;
using System.Threading;

class ShowFinalizerThread
{
    static Random rng = new Random();

    ~ShowFinalizerThread()
    {
        Console.WriteLine("Thread/domain: {0}/{1}",
                          Thread.CurrentThread.ManagedThreadId,
                          AppDomain.CurrentDomain.FriendlyName);
        if (rng.Next(10) == 0)
        {
            Console.WriteLine("Hanging!");
            Thread.Sleep(2000);
        }
    }

    static void Main()
    {
        new Thread(LoopForever).Start();
    }

    static void LoopForever()
    {
        while (true)
        {
            new ShowFinalizerThread();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            Thread.Sleep(300);
        };
    }
}

Compile each as a console app, then run test.exe (from the command line is easiest, IMO). You'll see that one app domain's finalizer blocks another.

In the future I wouldn't be surprised to see one finalizer thread per core rather than per AppDomain - but it sounds like you'll still have problems :(

You have my deepest sympathy (though not a solution) - once I tracked down a deadlock in an Oracle Blob. We were able to fix that by disposing of it properly, but I know not everything works that nicely - and it was a real pain even finding that one!

like image 181
Jon Skeet Avatar answered Nov 15 '22 23:11

Jon Skeet