Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can Unhandled Exceptions in Child AppDomains be prevented from crashing the main process?

I'm writing a small plugins library which uses app domains to isolate plugins using .Net framework 4.0. Hence the code that goes in each plugin is out of my control. When an unhandled exception is raised in one of the plugins, I have observed the results to be kind of a mixed bag. They are as follows.

When the unhandled exception gets thrown in the plugin's main thread, the main pluggable app calling the plugin's execute method is able to catch and handle it cleanly. No problems there. However,

  1. If the plugin starts a message loop for a WinForms based app in the plugin's Execute method and an unhandled exception gets thrown in the WinForm application (i.e. in a form) then the pluggable app can only catch the exception if its running from inside visual studio debugger. Otherwise (when invoked outside VS) the main pluggable app crashes along with the plugins.

  2. If the unhandled exception is thrown in a separate thread spawned by the plugin's Execute method, then the pluggable app has no chance of catching the exception and it crashes.

I have created a simple VS 2010 project to simulate this behaviour at the link below. http://www.mediafire.com/file/1af3q7tzl68cx1p/PluginExceptionTest.zip

In it the main method in the pluggable app looks like this

namespace PluginExceptionTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Press enter to load plugin");
            Console.ReadLine();

            Assembly entryAsm = Assembly.GetEntryAssembly();
            string assemblyFileName = Path.Combine(Path.GetDirectoryName(entryAsm.Location), "EvilPlugin.exe");


            AppDomainSetup domainSetup = new AppDomainSetup();
            AppDomain domain = AppDomain.CreateDomain("PluginDomain", null, domainSetup);
            PluginBase plugin = (PluginBase)domain.CreateInstanceFromAndUnwrap(assemblyFileName, "EvilPlugin.Plugin");

            Console.WriteLine("Plugin Loaded.");

            //SCENARIO 1: WinForms based plugin
            Console.WriteLine("Press Enter to execute winforms plugin. (Remember to click on the button in the form to raise exception)");
            Console.ReadLine();

            try
            {
                plugin.ExecuteWinApp();
            }
            catch (Exception)
            {
                //The exception is caught and this gets executed only when running in visual studio debugger. Else application exits. Why?
                Console.WriteLine("WinForms plugin exception caught. However same does not happen when run out of visual studio debugger. WHY?");
            }

            //SCENARIO 2: WinForms based plugin
            Console.WriteLine("Press Enter to execute threading plugin, wait for 3 seconds and the app will exit. How to prevent app from exiting due to this?");
            Console.ReadLine();
            try
            {
                plugin.ExecuteThread();
            }
            catch (Exception)
            {
                //This never gets executed as the exception is never caught. Application exits. Why?
                Console.WriteLine("WinForms plugin exception caught");
            }

            Console.ReadLine();
        }
    }
}

This is the code for the plugin project. It inherits from the PluginBase class in the pluggable app project above.

namespace EvilPlugin
{
    public class Plugin:PluginBase
    {
        public Plugin():base()
        {

        }

        public override void ExecuteWinApp()
        {            
            Application.Run(new Form1());            
        }

        public override void ExecuteThread()
        {
            Thread t = new Thread(new ThreadStart(RaiseEx));           
            t.Start();
        }

        private void RaiseEx()
        {
            Thread.Sleep(3000);
            throw new Exception("Another Evil Exception in a seperate thread");
        }
    }
}

Finally this is the code from the Form in the plugin

namespace EvilPlugin
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnException_Click(object sender, EventArgs e)
        {
            throw new Exception("Evil Exception");
        }
    }
}

How can i prevent the main process from exiting due to the two scenarios (1 and 2) mentioned above?

Thanks in advance.

like image 843
Harindaka Avatar asked Jun 29 '11 12:06

Harindaka


1 Answers

For handling WinForms exception. You could set "trap" for such ThreadExceptions in PluginBase class:

public abstract class PluginBase:MarshalByRefObject
{
    protected PluginBase()
    {
        System.Windows.Forms.Application.ThreadException +=
            (o, args) =>
            {
                throw new Exception("UntrappedThread Exception:" + args.Exception);
            };

    }

    public abstract void ExecuteWinApp();
    public abstract void ExecuteThread();
}

As by default, it will show you "The Exception Window". But will got to this handler if provided.

As for catching exception from other thread. There is no way. The best you could do is have a notification about Exception being thrown.

    AppDomain domain = AppDomain.CreateDomain("PluginDomain", null, domainSetup);
    domain.UnhandledException +=
        (o, eventArgs) =>
            {
                Console.WriteLine("Exception was caught from other AppDomain: " + eventArgs.ExceptionObject);
                Console.WriteLine("CLR is terminating?: " + eventArgs.IsTerminating);
            };
like image 168
DiVan Avatar answered Sep 22 '22 10:09

DiVan