Exception handling in AppDomain threads

G

Guest

Hi,

I'm trying to build a robust application framework that will be used by
other developers in the future. Their applications should be able to recover
themselves if they are crashing.

So, I thought, the best way to handle this is to lauch their stuff in a
separate AppDomain and when that AppDomain crashes, unload it and restart it.
This seems to work fine, unless an unhandled exception occurs in a
background thread that is started from that new AppDomain. Instead of just
that AppDomain crashing, the complete application is closed.

Does anyone know a way to prevent the application to close if an unhandled
exception occurs in a background thread that is started from another
AppDomain?

Any help is welcome,

Marc

Below is a test application that demonstrates what goes wrong. The goal is
to keep the application running while changing nothing in the Tester class,
because that is the part that will be written by other developers. I have no
control over them if they do something stupid like not handling any
exceptions in background threads.

<code>
using System;

using System.Reflection;

using System.Threading;

using System.Windows.Forms;

namespace CrashRecoveryTest

{

internal class Program : MarshalByRefObject

{

private static void Main(string[] args)

{

try

{

Application.ThreadException += Application_ThreadException;

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

AppDomain.CurrentDomain.UnhandledException +=
CurrentDomain_UnhandledException;

Console.WriteLine("Starting new AppDomain");

for (int i = 0; i < 3; i++)

{

AppDomain newDomain = AppDomain.CreateDomain("TestDomain");

newDomain.UnhandledException += newDomain_UnhandledException;

Console.WriteLine("Loading Tester");

Tester tester =
(Tester)newDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().GetName().Name, typeof(Tester).FullName);

Console.WriteLine("Calling Tester.Run method");

try

{

tester.Run();

}

catch (Exception ex)

{

Console.WriteLine("Caught exception " + ex.Message);

}

Console.WriteLine("Unloading AppDomain");

newDomain.UnhandledException -= newDomain_UnhandledException;

try

{

AppDomain.Unload(newDomain);

}

catch (Exception ex)

{

Console.WriteLine("Cannot unload AppDomain: " + ex.Message);

}

}

}

catch (Exception ex)

{

Console.WriteLine("Exception occurred: "+ex.Message );

}

Console.WriteLine("Application ended ok");

}

private static void Application_ThreadException(object sender,
ThreadExceptionEventArgs e)

{

Console.WriteLine("Application.ThreadException occurred");

}

private static void CurrentDomain_UnhandledException(object sender,
UnhandledExceptionEventArgs e)

{

Console.WriteLine("Unhandled exception occurred in default AppDomain");

}

private static void newDomain_UnhandledException(object sender,
UnhandledExceptionEventArgs e)

{

Console.WriteLine("Exception occurred in TestDomain");

}

}

public class Tester : MarshalByRefObject

{

public Tester()

{

Console.WriteLine("Tester is started in AppDomain " +
AppDomain.CurrentDomain.FriendlyName);

}

public void Run()

{

Console.WriteLine("Starting worker thread...");

Thread workerThread = new Thread(DoWork);

workerThread.Start();

Console.WriteLine("Doing some interesting work...");

for (int i = 0; i < 10; i++)

Thread.Sleep(1000);

workerThread.Join();

}

private static void DoWork()

{

Console.WriteLine("Worker thread is running in AppDomain "+
AppDomain.CurrentDomain.FriendlyName);

Thread.Sleep(3000);

Console.WriteLine("Worker thread about to throw an exception...");

throw new ApplicationException("Oops");

}

}

}

</code>
 
V

Vadym Stetsiak

Hello, Marc!


With best regards, Vadym Stetsiak.
Blog: http://vadmyst.blogspot.com

You wrote on Thu, 1 Nov 2007 12:45:00 -0700:

MS> Hi,

MS> I'm trying to build a robust application framework that will be used
MS> by other developers in the future. Their applications should be
MS> able to recover themselves if they are crashing.

MS> So, I thought, the best way to handle this is to lauch their stuff
MS> in a separate AppDomain and when that AppDomain crashes, unload it
MS> and restart it.
MS> This seems to work fine, unless an unhandled exception occurs in a
MS> background thread that is started from that new AppDomain. Instead
MS> of just that AppDomain crashing, the complete application is
MS> closed.

MS> Does anyone know a way to prevent the application to close if an
MS> unhandled exception occurs in a background thread that is started
MS> from another
MS> AppDomain?

MS> Any help is welcome,

MS> Marc

MS> Below is a test application that demonstrates what goes wrong. The
MS> goal is to keep the application running while changing nothing in
MS> the Tester class, because that is the part that will be written by
MS> other developers. I have no control over them if they do something
MS> stupid like not handling any exceptions in background threads.

MS> <code>
MS> using System;

MS> using System.Reflection;

MS> using System.Threading;

MS> using System.Windows.Forms;

MS> namespace CrashRecoveryTest

MS> {

MS> internal class Program : MarshalByRefObject

MS> {

MS> private static void Main(string[] args)

MS> {

MS> try

MS> {

MS> Application.ThreadException += Application_ThreadException;

MS> Application.SetUnhandledExceptionMode(UnhandledExceptionMode.
MS> CatchException);

MS> AppDomain.CurrentDomain.UnhandledException +=
MS> CurrentDomain_UnhandledException;

MS> Console.WriteLine("Starting new AppDomain");

MS> for (int i = 0; i < 3; i++)

MS> {

MS> AppDomain newDomain = AppDomain.CreateDomain("TestDomain");

MS> newDomain.UnhandledException += newDomain_UnhandledException;

MS> Console.WriteLine("Loading Tester");

MS> Tester tester =
MS> (Tester)newDomain.CreateInstanceAndUnwrap(Assembly.
MS> GetExecutingAssembly().GetName().Name, typeof(Tester).FullName);

MS> Console.WriteLine("Calling Tester.Run method");

MS> try

MS> {

MS> tester.Run();

MS> }

MS> catch (Exception ex)

MS> {

MS> Console.WriteLine("Caught exception " + ex.Message);

MS> }

MS> Console.WriteLine("Unloading AppDomain");

MS> newDomain.UnhandledException -= newDomain_UnhandledException;

MS> try

MS> {

MS> AppDomain.Unload(newDomain);

MS> }

MS> catch (Exception ex)

MS> {

MS> Console.WriteLine("Cannot unload AppDomain: " + ex.Message);

MS> }

MS> }

MS> }

MS> catch (Exception ex)

MS> {

MS> Console.WriteLine("Exception occurred: "+ex.Message );

MS> }

MS> Console.WriteLine("Application ended ok");

MS> }

MS> private static void Application_ThreadException(object sender,
MS> ThreadExceptionEventArgs e)

MS> {

MS> Console.WriteLine("Application.ThreadException occurred");

MS> }

MS> private static void CurrentDomain_UnhandledException(object sender,
MS> UnhandledExceptionEventArgs e)

MS> {

MS> Console.WriteLine("Unhandled exception occurred in default
MS> AppDomain");

MS> }

MS> private static void newDomain_UnhandledException(object sender,
MS> UnhandledExceptionEventArgs e)

MS> {

MS> Console.WriteLine("Exception occurred in TestDomain");

MS> }

MS> }

MS> public class Tester : MarshalByRefObject

MS> {

MS> public Tester()

MS> {

MS> Console.WriteLine("Tester is started in AppDomain " +
MS> AppDomain.CurrentDomain.FriendlyName);

MS> }

MS> public void Run()

MS> {

MS> Console.WriteLine("Starting worker thread...");

MS> Thread workerThread = new Thread(DoWork);

MS> workerThread.Start();

MS> Console.WriteLine("Doing some interesting work...");

MS> for (int i = 0; i < 10; i++)

MS> Thread.Sleep(1000);

MS> workerThread.Join();

MS> }

MS> private static void DoWork()

MS> {

MS> Console.WriteLine("Worker thread is running in AppDomain "+
MS> AppDomain.CurrentDomain.FriendlyName);

MS> Thread.Sleep(3000);

MS> Console.WriteLine("Worker thread about to throw an exception...");

MS> throw new ApplicationException("Oops");

MS> }

MS> }

MS> }

MS> </code>
 
V

Vadym Stetsiak

Hello, Marc!

Ignore previous message...

The behavior you observe is by design (see
http://msdn2.microsoft.com/en-us/library/ms228965.aspx ).

If you want to recover from unhadled exceptions - your framework has to be
in control of every operation that 3-d party code.
Code must be run within try-catch blocks.

However, swallowing exception is not good idea. IMO it is better to crash
with a lot of noise then swallow exception and have inconsistent application
state.
--
With best regards, Vadym Stetsiak.
Blog: http://vadmyst.blogspot.com

You wrote on Thu, 1 Nov 2007 12:45:00 -0700:

MS> Hi,

MS> I'm trying to build a robust application framework that will be used
MS> by other developers in the future. Their applications should be
MS> able to recover themselves if they are crashing.

MS> So, I thought, the best way to handle this is to lauch their stuff
MS> in a separate AppDomain and when that AppDomain crashes, unload it
MS> and restart it.
MS> This seems to work fine, unless an unhandled exception occurs in a
MS> background thread that is started from that new AppDomain. Instead
MS> of just that AppDomain crashing, the complete application is
MS> closed.

MS> Does anyone know a way to prevent the application to close if an
MS> unhandled exception occurs in a background thread that is started
MS> from another
MS> AppDomain?

MS> Any help is welcome,

MS> Marc

MS> Below is a test application that demonstrates what goes wrong. The
MS> goal is to keep the application running while changing nothing in
MS> the Tester class, because that is the part that will be written by
MS> other developers. I have no control over them if they do something
MS> stupid like not handling any exceptions in background threads.

MS> <code>
MS> using System;

MS> using System.Reflection;

MS> using System.Threading;

MS> using System.Windows.Forms;

MS> namespace CrashRecoveryTest

MS> {

MS> internal class Program : MarshalByRefObject

MS> {

MS> private static void Main(string[] args)

MS> {

MS> try

MS> {

MS> Application.ThreadException += Application_ThreadException;

MS> Application.SetUnhandledExceptionMode(UnhandledExceptionMode.
MS> CatchException);

MS> AppDomain.CurrentDomain.UnhandledException +=
MS> CurrentDomain_UnhandledException;

MS> Console.WriteLine("Starting new AppDomain");

MS> for (int i = 0; i < 3; i++)

MS> {

MS> AppDomain newDomain = AppDomain.CreateDomain("TestDomain");

MS> newDomain.UnhandledException += newDomain_UnhandledException;

MS> Console.WriteLine("Loading Tester");

MS> Tester tester =
MS> (Tester)newDomain.CreateInstanceAndUnwrap(Assembly.
MS> GetExecutingAssembly().GetName().Name, typeof(Tester).FullName);

MS> Console.WriteLine("Calling Tester.Run method");

MS> try

MS> {

MS> tester.Run();

MS> }

MS> catch (Exception ex)

MS> {

MS> Console.WriteLine("Caught exception " + ex.Message);

MS> }

MS> Console.WriteLine("Unloading AppDomain");

MS> newDomain.UnhandledException -= newDomain_UnhandledException;

MS> try

MS> {

MS> AppDomain.Unload(newDomain);

MS> }

MS> catch (Exception ex)

MS> {

MS> Console.WriteLine("Cannot unload AppDomain: " + ex.Message);

MS> }

MS> }

MS> }

MS> catch (Exception ex)

MS> {

MS> Console.WriteLine("Exception occurred: "+ex.Message );

MS> }

MS> Console.WriteLine("Application ended ok");

MS> }

MS> private static void Application_ThreadException(object sender,
MS> ThreadExceptionEventArgs e)

MS> {

MS> Console.WriteLine("Application.ThreadException occurred");

MS> }

MS> private static void CurrentDomain_UnhandledException(object sender,
MS> UnhandledExceptionEventArgs e)

MS> {

MS> Console.WriteLine("Unhandled exception occurred in default
MS> AppDomain");

MS> }

MS> private static void newDomain_UnhandledException(object sender,
MS> UnhandledExceptionEventArgs e)

MS> {

MS> Console.WriteLine("Exception occurred in TestDomain");

MS> }

MS> }

MS> public class Tester : MarshalByRefObject

MS> {

MS> public Tester()

MS> {

MS> Console.WriteLine("Tester is started in AppDomain " +
MS> AppDomain.CurrentDomain.FriendlyName);

MS> }

MS> public void Run()

MS> {

MS> Console.WriteLine("Starting worker thread...");

MS> Thread workerThread = new Thread(DoWork);

MS> workerThread.Start();

MS> Console.WriteLine("Doing some interesting work...");

MS> for (int i = 0; i < 10; i++)

MS> Thread.Sleep(1000);

MS> workerThread.Join();

MS> }

MS> private static void DoWork()

MS> {

MS> Console.WriteLine("Worker thread is running in AppDomain "+
MS> AppDomain.CurrentDomain.FriendlyName);

MS> Thread.Sleep(3000);

MS> Console.WriteLine("Worker thread about to throw an exception...");

MS> throw new ApplicationException("Oops");

MS> }

MS> }

MS> }

MS> </code>
 
G

Guest

Vadym Stetsiak said:
Hello, Marc!

Ignore previous message...

The behavior you observe is by design (see
http://msdn2.microsoft.com/en-us/library/ms228965.aspx ).

If you want to recover from unhadled exceptions - your framework has to be
in control of every operation that 3-d party code.
Code must be run within try-catch blocks.

However, swallowing exception is not good idea. IMO it is better to crash
with a lot of noise then swallow exception and have inconsistent application
state.
Vladym, thanks for your quick response..

As I unload the AppDomain that created the thread and restart the whole
thing all over again, I don't think you can call the application to be in an
inconsistent state.
All DLLs that were loaded are released from memory and loaded all over
again. To me that is the same then restartin the application, no?

I thought AppDomains were designed to avoid an application crashing
completely when only a part of it fails...

Marc
 

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