Re: Exception handling in AppDomain threads
by Vadym Stetsiak on 11/2/2007 2:36:00 AM
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>
Re: Exception handling in AppDomain threads
by Vadym Stetsiak on 11/2/2007 2:45:00 AM
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>