c# - Unhandled exception coming from GC thread when a static-constructor / type-initializer fails -
(title was: "typeloadexception not wrapped targetinvocationexception reflection used")
with bltoolkit figured out interesting fact - methodinfo.invoke not catching exception in calling method.
see example - emulates exception in static constructor method, invoking via reflection.
problem testcomponent inherits component , have overridden dispose method. in sample 2 messages - 1 "handle" , 1 "unhandle" - seems components have different handling inside reflection on lower levels.
if comment out method dispose(bool disposing) - receive "handle" message.
can give explanation why happen , propose solution? try-catch inside bltoolkit cannot marked answer - not member of 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); } } }
this isn't related reflection; similar result 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 writes "happy"; unhandled exception seems coming gc, presumably trying collect partially constructed object (it never calls instance constructor), , falling over... in particular note on different threads (i'm pretty sure unhandled on gc thread). being gc/threaded can pain repro; added gc.collect(gc.maxgeneration, gccollectionmode.forced);
locally, force gc happen see more often. fascinating.
since neither instance constructor (testcomponent()
) nor finalizer (~testcomponent()
) invoked, have no possible way (that can tell) of fixing this.
the main thing can suggest here, sadly, is: don't have type initializers fail :(
the 1 thing can working cheat runtime:
object obj = formatterservices.getuninitializedobject(typeof(testcomponent));
this still hits type initializer , fails, object doesn't seem quite lost. maybe route doesn't mark finalization. , gc doesn't hate quite much.
you still going have major problems ever using object if type initializer borked.
so in code, might have:
class componentexecutor { public void do() { using (var tc = (testcomponent) formatterservices.getuninitializedobject(typeof(testcomponent))) { // call ctor manually typeof(testcomponent).getconstructor(type.emptytypes).invoke(tc, null); // maybe can skip since `using` gc.reregisterforfinalize(tc); tc.do(); } } }
now:
- when type initializer fails, nothing finalized (it doesn't need - object can't possibly have @ point, since never ran constructor)
- when type initializer works, ctor executed , registered gc - hunky dory
Comments
Post a Comment