Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unhandled exception coming from GC thread when a static-constructor / type-initializer fails

(title was: "TypeLoadException not always wrapped into TargetInvocationException with Reflection used")

With BLToolkit I figured out interesting fact - methodInfo.Invoke not always catching exception in the calling method.

See example - it emulates exception in the static constructor for the method, invoking via reflection.

Problem is that TestComponent inherits from Component AND have overridden Dispose method. So in this sample will be 2 messages - one "handle" and one "unhandle" - seems like Components have different handling inside Reflection on lower levels.

If we comment out method Dispose(bool disposing) - we will receive only "handle" message.

Can anyone give explanation why does it happen and propose a solution? Try-catch inside BLToolkit cannot be marked as answer - I am not member of their team :)

    class Program
{
    static void Main()
    {
        AppDomain.CurrentDomain.UnhandledException +=
            (sender, eventArgs) => Console.WriteLine("unHandled " + eventArgs.ExceptionObject.GetType().FullName);
        try
        {
            try
            {
                var instance = Activator.CreateInstance(typeof(ComponentExecutor));
                MethodInfo mi = typeof(ComponentExecutor).GetMethod("Do");
                BindingFlags bf = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly |
                                  BindingFlags.InvokeMethod;

                mi.Invoke(instance, bf, null, new object[0], CultureInfo.InvariantCulture);
            }
            catch (TargetInvocationException tarEx)
            {
                throw tarEx.InnerException;
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Handled " + ex.GetType().FullName);
        }
    }

    class ComponentExecutor
    {
        public void Do()
        {
            new TestComponent().Do();
        }
    }
    class TestComponent : Component
    {
        static TestComponent()
        {
            throw new NullReferenceException();
        }
        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Do()
        {
            Console.WriteLine("Doing");
        }
        protected override void Dispose(bool disposing)
        {
            Console.WriteLine("Disposing");
            base.Dispose(disposing);
        }
    }
}
like image 711
Lonli-Lokli Avatar asked Jan 25 '11 08:01

Lonli-Lokli


1 Answers

This isn't related to reflection; you get a very similar result with just:

    AppDomain.CurrentDomain.UnhandledException +=
        (sender, eventArgs) => Console.WriteLine("unHandled " + eventArgs.ExceptionObject.GetType().FullName);
    try
    {
        new TestComponent();
    }
    catch (Exception ex)
    {
        Console.WriteLine("Handled " + ex.GetType().FullName);
    }
    Console.WriteLine("happy");

It actually writes "happy"; the unhandled exception seems to be coming from GC, presumably with it trying to collect a partially constructed object (it never calls the instance constructor), and this is then falling over... in particular note that they are on different threads (I'm pretty sure the unhandled is on the GC thread). Being GC/threaded it can be a pain to repro; I added GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); locally, just to force GC to happen so I see it more often. Fascinating.

Since neither an instance constructor (TestComponent()) nor finalizer (~TestComponent()) get invoked, you have no possible way (that I can tell) of fixing this.

The main thing I can suggest here, sadly, is: don't have type initializers fail :(

The one thing I can get working is to cheat the runtime:

object obj = FormatterServices.GetUninitializedObject(typeof(TestComponent));

This still hits the type initializer and fails, but the object doesn't seem to be quite as lost. Maybe this route doesn't mark it for finalization. And thus GC doesn't hate it quite as much.

You are still going to have major problems ever using this object if the type initializer is borked.

So in your code, you might have:

class ComponentExecutor
{
    public void Do()
    {
        using (var tc = (TestComponent)
                FormatterServices.GetUninitializedObject(typeof(TestComponent)))
        {
            // call the ctor manually
            typeof(TestComponent).GetConstructor(Type.EmptyTypes).Invoke(tc, null);
            // maybe we can skip this since we are `using`
            GC.ReRegisterForFinalize(tc); 
            tc.Do();
        }
    }
}

now:

  • when the type initializer fails, nothing is finalized (it doesn't need to be - the object can't possibly have anything to do at this point, since it never ran a constructor)
  • when the type initializer works, the ctor is executed and it is registered with GC - all hunky dory
like image 178
Marc Gravell Avatar answered Nov 05 '22 05:11

Marc Gravell