.NET Forum / .NET Framework / New Users / March 2006
Preventing Exceptions from bringing down app
|
|
Thread rating:  |
Michael Bray - 18 Mar 2006 04:03 GMT I'm writing a library to support provide plugin capability to my applications. It does this by loading DLL's into a new AppDomain for each plugin that is loaded. Now obviously when I write a plugin, I can make sure that my plugins don't throw any exceptions. But I certainly can't guarantee that other people writing plugins won't throw an exception. The problem is that if one of these other plugins throws an exception, it brings down the entire application.
Is there anything I can do to prevent Exceptions in other AppDomains from bringing down the entire app, when I don't own the code that is running in that AppDomain? I thought I would be able to use AppDomain.UnhandledException, but as is pointed out in the link below, this is only a "notification" not a "handler".
UnhandledException is not a handler: (watch the wrap) http://lab.msdn.microsoft.com/ProductFeedback/viewFeedback.aspx? feedbackId=FDBK21092
-mdb
Kevin Spencer - 18 Mar 2006 14:17 GMT Without going into a lot of detail, here's an example. Modify it any way you need to:
// Setup unhandled exception handlers AppDomain.CurrentDomain.UnhandledException += // CLR new UnhandledExceptionEventHandler(OnUnhandledException);
Application.ThreadException += // Windows Forms new System.Threading.ThreadExceptionEventHandler( OnGuiUnhandedException);
// CLR unhandled exception private static void OnUnhandledException(Object sender, UnhandledExceptionEventArgs e) { HandleUnhandledException(e.ExceptionObject); }
// Windows Forms unhandled exception private static void OnGuiUnhandedException(Object sender, ThreadExceptionEventArgs e) { HandleUnhandledException(e.Exception); }
static void HandleUnhandledException(Object o) { Exception e = o as Exception;
if (e != null) { // Report System.Exception info Debug.WriteLine("Exception = " + e.GetType()); Debug.WriteLine("Message = " + e.Message); Debug.WriteLine("FullText = " + e.ToString()); Utilities.HandleError(e); // My own method that simply logs the exception details } else { // Report exception Object info Debug.WriteLine("Exception = " + o.GetType()); Debug.WriteLine("FullText = " + o.ToString()); Utilities.LogError("Exception: " + o.GetType().ToString() + "\r\nFullText: " + o.ToString()); // My own method that simply logs the exception details }
MessageBox.Show("An unhandled exception occurred " + "and the application is shutting down."); Environment.Exit(1); // Shutting down
 Signature HTH,
Kevin Spencer Microsoft MVP .Net Developer
Presuming that God is "only an idea" - Ideas exist. Therefore, God exists.
> I'm writing a library to support provide plugin capability to my > applications. It does this by loading DLL's into a new AppDomain for each [quoted text clipped - 16 lines] > > -mdb Michael Bray - 18 Mar 2006 15:08 GMT > Without going into a lot of detail, here's an example. Modify it any > way you need to: [quoted text clipped - 6 lines] > new System.Threading.ThreadExceptionEventHandler( > OnGuiUnhandedException); I've already tried using AppDomain.CurrentDomain.UnhandledException as well as Application.ThreadException. Neither one of them prevents the exception from shutting the application down. Sure I can capture the event and say "Hey... This plugin just caused an exception". But I can't prevent that exception from killing the application. That's what I'm really trying to get, so I can unload that faulty AppDomain but allow my program (and any other plugins in other AppDomains) to continue working.
-mdb
Kevin Spencer - 18 Mar 2006 17:16 GMT Are these DLLs .Net assemblies? Can you provide more information about how they interact with your application?
 Signature HTH,
Kevin Spencer Microsoft MVP .Net Developer
Presuming that God is "only an idea" - Ideas exist. Therefore, God exists.
>> Without going into a lot of detail, here's an example. Modify it any >> way you need to: [quoted text clipped - 18 lines] > > -mdb Michael Bray - 18 Mar 2006 19:15 GMT > Are these DLLs .Net assemblies? Can you provide more information about > how they interact with your application? Better than that - I'll package up the Solution so you can see for yourself. I've posted it at
http://users.ctinet.net/mbray/PluginTest.zip
The program is 'PluginTest.exe'. When it loads up, click the main menu item called 'LoadPlugin'. It will ask you for a DLL to load, and looks in the directory "Plugins" underneath the PluginTest\bin\Debug directory.
The plugin loader will create an AppDomain and a proxy object inside that domain, and then call the proxy object, which loads the DLL into its own AppDomain.
When the plugin starts, it will initiate a worker process in the background and begin calling the functions that the host process provides. I've hardcoded the plugin so that on the (n % 10 == 9) interation, it will throw an exception.
You will see that both the AppDomain.UnhandledException and the Appliation.ThreadException receive notification of the exception, but can do nothing to stop it from terminating the main program. The target, of course, is for the main program to be able to say "OK Plugin X had an exception so I'm going to unload it's AppDomain and continue on with the rest of my work".
Let me also say (or repeat) that I understand that I could run a process on the host that calls functions on the plugin, wrapped in a try/catch and that would probably work. But I would like my plugins to be in charge of their own destiny, and not have to rely on some kind of polling from the hosting application.
-mdb
Naveen - 19 Mar 2006 05:30 GMT Hi, This is a change in .NET 2.0. Here is a clear write up about the same. http://msdn.microsoft.com/netframework/programming/breakingchanges/runtime/clr.aspx
This is a breaking change. It is supposed to be done in v1.1 itself.
But there is a workaround create an App.Config and include this entry
<runtime> <legacyUnhandledExceptionPolicy enabled="true" /> </runtime>
My suggestion would be is to not to use this and try and fix the problem.
Michael Bray - 19 Mar 2006 05:59 GMT > Hi, > This is a change in .NET 2.0. Here is a clear write up about [quoted text clipped - 12 lines] > My suggestion would be is to not to use this and try and fix the > problem. Wow... ok that makes sense as to why I'm seeing this behavior. I can handle this if I can somehow prevent code in my AppDomain from creating threads. I've been looking at the CAS SecurityPermission's ControlThread but it doesn't seem to do what I want. Any suggestions?
-mdb
Naveen - 19 Mar 2006 18:04 GMT I dont know how preventing of thread creation would avoid this. But ControlThread would not stop from creating a thread. It can prevent a thread from being suspended.
Michael Bray - 19 Mar 2006 20:44 GMT > I dont know how preventing of thread creation would avoid this. But > ControlThread would not stop from creating a thread. It can prevent a > thread from being suspended. Its not that it would prevent the problem per se, but if I can prevent the plugin from creating threads, then I can pretty much guarantee that I can catch an exception, because I can run the plugin inside a try/catch block. As it stands now, the plugin can create threads, and I cannot catch exceptions that might occur in that thread, and that's what causes the problem.
-mdb
Naveen - 19 Mar 2006 20:51 GMT Ok. What if the plugin uses the ThreadPool? How are you planning to handle that? Another option is to use HostProtectionAttribute. But to use this application has to hosted with the HostProtection truned on (similar to SQL Server 2005).
Naveen - 19 Mar 2006 21:11 GMT Hi, Here is a sample as to how to use HostProtection to prevent creation of threads.
http://blogs.msdn.com/shawnfa/archive/2005/10/13/480210.aspx
Naveen
Michael Bray - 20 Mar 2006 04:58 GMT > http://blogs.msdn.com/shawnfa/archive/2005/10/13/480210.aspx Thanks! To answer your previous question, I figured creation of a ThreadPool thread would be restricted in the same way that creation of a normal thread would be.
-mdb
Michael Bray - 20 Mar 2006 05:15 GMT > Here is a sample as to how to use HostProtection to prevent > creation of threads. Naveen,
Thanks for that link, but unfortunately, I don't think I can use it. It seems that the method he describes would affect my host application as well as the plugins. This is not the behavior I'm looking for. I only want to prevent creation of new threads from the plugins (which I load into a new AppDomain). I still need to be able to create threads in the host application. Am I correct in this analysis?
-mdb
Michael Bray - 20 Mar 2006 05:20 GMT > Thanks for that link, but unfortunately, I don't think I can use it. > It seems that the method he describes would affect my host application > as well as the plugins. This is not the behavior I'm looking for. I > only want to prevent creation of new threads from the plugins (which I > load into a new AppDomain). I still need to be able to create threads > in the host application. Am I correct in this analysis? Here's another idea... If I can't prevent code that I specify from creating new threads, is there anything that I can use to determine if code makes calls to certain functions, such as Thread.Start or ThreadPool.QueueUserWorkItem? If so, then I could simply decide simply to disallow loading that particular plugin...
-mdb
Naveen - 20 Mar 2006 21:14 GMT Hi, This can be configured for the plugins alone. By this way the host application would be able to create threads and manipulate with them where as the plugins cannot do that. This is the whole idea of Hosting Interfaces. So this is how when an application with Sql Server crashes it wont crash the SQL Server. I hope i answered youre question.
Naveen
Michael Bray - 21 Mar 2006 06:31 GMT > This can be configured for the plugins alone. By this way the > host application would be able to create threads and manipulate with > them where as the plugins cannot do that. This is the whole idea of > Hosting Interfaces. So this is how when an application with Sql Server > crashes it wont crash the SQL Server. I hope i answered youre question. OK So I have this working now - quite nicely actually. I used the ADMHost sample and worked it out so that the host application can create threads on the plugin's behalf, catching any exceptions that occur, but the plugins can't create threads directly. This is EXACTLY what I was hoping for.
Now I only have one problem... The way that my application gets started is that the ADMHost starts up, and then calls ExecuteAssembly on my EXE file. This would be fine, except that ADMHost runs in a text command window. So currently, in order to gain the benefit of these controlled AppDomains, I have this DOS window floating on my desktop.
What can I do to prevent this window from appearing? Anyone have any suggestions?
-mdb
42 - 20 Mar 2006 19:10 GMT > I'm writing a library to support provide plugin capability to my > applications. It does this by loading DLL's into a new AppDomain for each [quoted text clipped - 13 lines] > http://lab.msdn.microsoft.com/ProductFeedback/viewFeedback.aspx? > feedbackId=FDBK21092 Couldn't you just wrap any calls to functions in the plugin interface in try/catch blocks?
For example you could write a pluginhost class that implements the plugin interface, and contains an actual plugin as a private member. All the plugin interface methods would be just be dispatched to the contained plugin but wrapped in try/catch constructs.
When you load a plugin you put it inside a pluginhost and interact with that.
Michael Bray - 20 Mar 2006 21:25 GMT > Couldn't you just wrap any calls to functions in the plugin interface > in try/catch blocks? [quoted text clipped - 3 lines] > All the plugin interface methods would be just be dispatched to the > contained plugin but wrapped in try/catch constructs. If my host application was calling the plugin functions, then yes, I could do what you are suggesting. The problem arises, however, because I am allowing the plugins to call functions on the host.
So, for example, when I initialize the plugin, I pass it a reference to the host with functions such as 'SetHostTitle'. So it is the plugin that is making the calls, not the host. If the plugin only runs on one thread, then this isn't a problem, because I can wrap the function that I call on the plugin in a try/catch. But if the plugin creates a new thread (to do background work) and that thread makes calls to the host, then I have no way to control that, and any exceptions thrown in that background thread will terminate the host application.
That's why I'm currently looking for ways to prevent the plugin from creating new threads. (I would provide a function on the host to create a thread and run a delegate function that the plugin specifies if the plugin wants to run something in the background, because again I could run it in a try/catch.)
I've looked at Code Access Protection but I don't think that will work because the attribute that allows 'Code Execution' (which I obviously need) also allows thread creation. There is a 'Control Thread' but it apparently only controls whether I can perform a Suspend on another thread, and doesn't prevent thread creation.
I've also looked at Host Protection Attributes, but I don't think that will work because (from what I can see) it has to be done in the CLR loader in native code and affects the entire process, which isn't what I want - I only want to restrict thread creation in specific AppDomains.
My current 'best option' is to decompile the plugin assembly with ILDASM before I load it and look for references to Thread.Start or ThreadPool.QueueUserWorkItem. It's not my preferred way to do it since there are easy ways around that, such as referencing a secondary dll that creates the thread for you.
This now feels like an area that the .NET 2.0 CLR developers forgot about. It certainly makes sense that Exceptions in threads should NOT be dropped silently, as I think they were in .NET 1.1, but we as the programmers should have a way to intercept those exceptions to try to do something about it.
-mdb
42 - 21 Mar 2006 00:55 GMT > > Couldn't you just wrap any calls to functions in the plugin interface > > in try/catch blocks? [quoted text clipped - 16 lines] > then I have no way to control that, and any exceptions thrown in that > background thread will terminate the host application. Yes. I see now what your issue is.
> That's why I'm currently looking for ways to prevent the plugin from > creating new threads.
> (I would provide a function on the host to create > a thread and run a delegate function that the plugin specifies if the [quoted text clipped - 17 lines] > there are easy ways around that, such as referencing a secondary dll > that creates the thread for you. That feels kludgy to me.
> This now feels like an area that the .NET 2.0 CLR developers forgot > about. It certainly makes sense that Exceptions in threads should NOT > be dropped silently, as I think they were in .NET 1.1, but we as the > programmers should have a way to intercept those exceptions to try to do > something about it. Er... they do it seems. The msdn page Naveem posted said:
----
Put a catch block at the top of your non-main thread, threadpool workitem, or finalizer. (Or else fix the bug that led to the exception.) Alternatively, in the section of the application's config file, add the following:
<legacyUnhandledExceptionPolicy enabled="1"/>
----
Naveem only mentioned the legacy route of enabling the old behaviour; which I agree seems pretty kludgy and dangerous. I think you should be seeing and at least logging those exceptions, even if you want to otherwise ignore them.
However; it clearly says you can catch them directly by putting a catch block "at the top of your non-main thread, threadpool workitem, or finalizer".
Have you experimented with that? Why is that not suitable for your needs??
------
As a totally different approach... have you considered running the "plug-ins" in a completley separate process. Then they can crash and burn without their child threads throwing unhandled exceptions back into your host application.
It would add some overhead to the interprocess communication; but it would give them the separation you are looking for, and the extra overhead might not cost too much if the plugins aren't too 'chatty'.
-regards, Dave
Naveen - 21 Mar 2006 01:20 GMT Hi, The hosting interfaces have ICLRPolicyManager::SetUnHandledExceptionPolicy which in turn maps to the legacyUnhandledExceptionPolicy . This is the only way in which you can ensure that the by an exception thrown by addin dose not kill the main process. The SQL Server 2005 has implemented the SetUnHandledExceptionPolicy that prevents the Yukon from crashing if the addin throws an exception.
And i dont understand as to how this would " pretty kludgy and dangerous" when Microsoft as implemented with in their Sql Server 2005.
Naveen
42 - 21 Mar 2006 02:12 GMT > Hi, > The hosting interfaces have > ICLRPolicyManager::SetUnHandledExceptionPolicy which in turn maps to > the legacyUnhandledExceptionPolicy . This is the only way in which you > can ensure that the by an exception thrown by addin dose not kill the > main process. The document you referenced yourself *seems* to indicate otherwise.
Further, my suggestion of launching it in a separate process would also isolate it from crashes in the "plugin".
At any rate, enabling "legacy" handling of unhandled exceptions is not the only way, and doesn't strike me as the best way.
> The SQL Server 2005 has implemented the > SetUnHandledExceptionPolicy that prevents the Yukon from crashing if > the addin throws an exception. > > And i dont understand as to how this would " pretty kludgy and > dangerous" An unhandled exception occurred. The plugin might be malfunctioning, returning bad data, corrupting data in your host application. I don't know you could consider it anything BUT dangerous.
I mean I understand why he doesn't want it bring down his whole application, but just going back to the old way of pretending it didn't happen doesn't seem very bright either.
> when Microsoft as implemented with in their Sql Server 2005. What has that got to do with anything? Perhaps it was the easiest way to get crash prone legacy stuff from becoming totally non-functional. Microsoft has made a lot security AND stability compromises over the years in the name of backwards compatibility.
Naveen - 21 Mar 2006 02:45 GMT Interprocess communication is very expensive. And if a process has to be created for an addin then if a host has to load 1000 addins then as per your advice it would create about 1000 process. In windows createprocess is one of costliest API that's the reason App Domains were created. For each process windows creates 10 threads then the application would end up with 10000 threads. Creating process for each addin is BAD, BAD idea. Read about the same here http://blogs.msdn.com/cbrumme/archive/2003/06/01/51466.aspx
42 - 21 Mar 2006 05:12 GMT > Interprocess communication is very expensive. True, there is considerable overhead. But whether or not it matters is a completely separate question. The application *might* need to do little more than activate the add-in with some inital parameters and forget about it for 20 minutes.
> And if a process has to > be created for an addin then if a host has to load 1000 addins then as > per your advice it would create about 1000 process. In windows > createprocess is one of costliest API that's the reason App Domains > were created. For each process windows creates 10 threads then the > application would end up with 10000 threads. 1000 addins that each need to spawn potentially unstable child threads to background processing is still a couple thousand threads. And if they start flaking out and malfunctioning but the exceptions aren't getting trapped you've still got a royal mess on your hands.
At any rate I'm sure we can come up with a worst case scenario for any proposal.
> Creating process for each addin is BAD, BAD idea. That really depends on how many *active* addins there are expected to be now doesn't it? Obviously in some cases it is a 'bad bad idea', but in others where the number of addins will be quite small, or where there may be a pile of addins but only one or two active at a given time, then it wouldn't be much of an issue at all.
Michael Bray - 21 Mar 2006 02:54 GMT > However; it clearly says you can catch them directly by putting a > catch block "at the top of your non-main thread, threadpool workitem, > or finalizer". > > Have you experimented with that? Why is that not suitable for your > needs?? Because it relies on the plugin programmer to do that. If I was the plugin programmer, then none of this would be a problem because I would guarantee to myself that I would wrap everything in a try/catch. But I can't make that guarantee about other programmers.
This ties into why I'm trying to prevent the plugin from creating new threads. Because if they don't create threads, then I can guarantee that the plugin host can capture any exceptions. But if they DO create threads, then I cannot do this.
> As a totally different approach... have you considered running the > "plug-ins" in a completley separate process. Then they can crash and > burn without their child threads throwing unhandled exceptions back > into your host application. Yeah I thought about it briefly... But I'm doing this for my own personal gratification as anything else, and I want to "do it the right way". I'm sure I could probably force things to work if I did it this way, but it wouldn't feel right.
Incidentally, I've been playing around with the ADMHost sample that Naveen pointed out, and I feel like I'm close to what I want... like it's on the other side of a wall that I just have to figure how to get around. I'm still trying to wrap my head around exactly what is going on in ADMHost, and there are a few things that are confounding me. Shawn Farkas' ADMHost modifications feel like the right way to go - I just haven't yet been successful in integrating it with my plugins library.
Right now, I can create a version of my application that can create threads, or I can create one that cannot create threads. I'm trying to figure out how to let plugin host create threads but not the plugins themselves. I'm getting closer.
Here's the ADMHost discussion just for reference: http://blogs.msdn.com/shawnfa/archive/2005/10/13/480210.aspx
-mdb
42 - 21 Mar 2006 07:33 GMT > > However; it clearly says you can catch them directly by putting a > > catch block "at the top of your non-main thread, threadpool workitem, [quoted text clipped - 4 lines] > > Because it relies on the plugin programmer to do that. Gotcha, I downloaded your example, and I see what you mean now.
> If I was the plugin > programmer, then none of this would be a problem because I would guarantee [quoted text clipped - 15 lines] > sure I could probably force things to work if I did it this way, but it > wouldn't feel right. Fair enough. Although I'm not sure what the 'right way' would be in this case, short of being able to trap those exceptions. Anything else seems a bit of a kludge.
FWIW, I also thought perhaps you could use the unhandled event handler to record state and your programs last gasp would be to launch itself in a new process where it could pick up the saved state and continue. But that's no less a kludge than anything else.
> Incidentally, I've been playing around with the ADMHost sample that Naveen > pointed out, and I feel like I'm close to what I want... like it's on the [quoted text clipped - 8 lines] > figure out how to let plugin host create threads but not the plugins > themselves. I'm getting closer. Yeah, it seems that you are close.
> Here's the ADMHost discussion just for reference: > http://blogs.msdn.com/shawnfa/archive/2005/10/13/480210.aspx I'm actually interested in this for a plugin architecture I'm working with myself -- although in my case I'm not concerned about unhandled exceptions, but more about malicious plugins. I'd like to restrict what they can do; not that I think I am likely to make it totally secure, but I was hoping to make some reasonable attempt at locking them down.
Good luck.
Michael Bray - 21 Mar 2006 08:10 GMT > Fair enough. Although I'm not sure what the 'right way' would be in > this case, short of being able to trap those exceptions. Anything else > seems a bit of a kludge. > Yeah, it seems that you are close. I actually have a pretty fair working demo now... There's only a few problems I'm currently experiencing:
1. The ADMHost starts up in a text window - more annoying than a problem 2. For some reason the plugin has to be in the same directory as the application if I want to call back to the host to create a thread for the plugin.
> I'm actually interested in this for a plugin architecture I'm working > with myself -- although in my case I'm not concerned about unhandled > exceptions, but more about malicious plugins. I'd like to restrict > what they can do; not that I think I am likely to make it totally > secure, but I was hoping to make some reasonable attempt at locking > them down. From what I've seen you should be able to utilize CAS pretty effectively here. Part of the process of restricting the plugin's ability to create threads is to grant it Execution permission but outside of a FullTrust frame. You could easily add or modify the list of permissions that are being given to match your local policy. Of course, there are only so many things you can restrict, but it should cover most of the typical security issues. (Access to file system, registry, etc.)
-mdb
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|