Preventing Exceptions from bringing down app

M

Michael Bray

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
 
K

Kevin Spencer

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

--
HTH,

Kevin Spencer
Microsoft MVP
..Net Developer

Presuming that God is "only an idea" -
Ideas exist.
Therefore, God exists.
 
M

Michael Bray

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);

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
 
K

Kevin Spencer

Are these DLLs .Net assemblies? Can you provide more information about how
they interact with your application?

--
HTH,

Kevin Spencer
Microsoft MVP
..Net Developer

Presuming that God is "only an idea" -
Ideas exist.
Therefore, God exists.
 
M

Michael Bray

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
 
M

Michael Bray

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/runt
ime/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.


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
 
N

Naveen

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.
 
M

Michael Bray

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
 
N

Naveen

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).
 
M

Michael Bray

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
 
M

Michael Bray

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
 
4

42

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

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.
 
N

Naveen

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
 
M

Michael Bray

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.

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
 
4

42

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.

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
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.

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
 
N

Naveen

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
 
4

42

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top