.NET 2.0 Application Restart When it Goes "KABOOM"

Shows how to use the AppDomain.UnhandledException event and similar events, to log and even restart your app if it blows up.

From our travels we have learned that in .NET 2.0, unlike in .NET 1.1, any running application that throws an unhandled exception will immediately cause the app to exit and the appDomain to unload. Even with the best - designed code and the very best testing and exception handling, it's still possible for a running app to hit some unknown condition that you haven't been able to anticipate during development, and proceed to blow up on you.

If this is an application that you need to have running, then you need some reliable way to get it back up again. For Windows Services, we have settings in the SCM that can handle this- we get three chances to set the application to restart itself if it fails. We can do this in the "Recovery" tab in the service's property sheet. But what do you do if it's not a service?

The key here is to understand two events that you can catch when an application is about to go into the "Black Hole" -- the Application.ThreadException event, and the AppDomain.CurrentDomain.UnhandledException event. Note that these are events, not "exception handlers". What that means is that when one of these fires, your app is already headed into the black hole and there isn't anything you can do about it. They are there in order to provide the developer a last opportunity to do logging and any cleanup before the AppDomain goes into Never-never Land.

Here's some sample code that illustrates how you can wire this up in your apps:

namespace UnhandledExceptionRestart

{

    static class Program

    {

        /// <summary>

        /// The main entry point for the application.

        /// </summary>

        [STAThread]

        static void Main()

        {

         // Adds the event handler to catch any exceptions that happen in
         //the main UI thread.

       Application.ThreadException += new ThreadExceptionEventHandler(OnThreadException);

 

            // add the event handler for all threads in the appdomain
            // except for the main UI thread

            AppDomain.CurrentDomain.UnhandledException +=

                new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

            Application.EnableVisualStyles();

            Application.Run(new Form1());

        }

 

        // Handles the exception event for all other threads

        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)

        {         

            Logger.WriteToLog(e.ExceptionObject.ToString());

            Assembly asm = Assembly.GetExecutingAssembly();

            string asmName = asm.GetName().Name + ".exe";

            Process.Start(System.Environment.CurrentDirectory + @"\" + asmName);

 

 

        }

 

        // Handles the exception event from a UI thread.

        static void OnThreadException(object sender, ThreadExceptionEventArgs t)

        {          

            Logger.WriteToLog(t.Exception.GetBaseException().ToString());

            Assembly asm = Assembly.GetExecutingAssembly();

            string asmName = asm.GetName().Name + ".exe";

            Process.Start(System.Environment.CurrentDirectory + @"\" + asmName);

        }

    }

}

What happens here is that when there is either an AppDomain unhandled exception (any thread except the main UI thread) or an Application ThreadException ( on the main UI thread) our events get fired. We do some logging, and then we use the Process class to restart a new instance of the application immediately. When the closing brace of either method is reached, that's the end for this instance, nothing else can be done.

My little sample Logger class just writes with a Filestream for demo purposes only:

using System;

using System.Collections.Generic;

using System.Text;

using System.IO;

namespace UnhandledExceptionRestart

{

   public class Logger

    {

       private Logger() { }

 

       public static void WriteToLog( string message)

       {

           using (FileStream fs = new FileStream(System.Environment.CurrentDirectory + @"\log.txt",

               FileMode.Append, FileAccess.Write))

           {

               message = DateTime.Now.ToString() + "\r\n" + message +                    "\r\n==========================================================\r\n";

               byte[] b = System.Text.Encoding.UTF8.GetBytes(message);

               fs.Write(b, 0, b.Length);

               fs.Close();

           }

       }

    }

}

In the sample Visual Studio 2005 solution that you can download below, I have a button on the Form that triggers the KABOOM method which simply attempts a division by zero:

private void button1_Click(object sender, EventArgs e)

        {

            // throw exception directly from event handler

            //GoBoom();           

            // throw exception from secondary thread

          Thread t = new Thread(GoBoom);

           t.Start();

        }

 

        void GoBoom()

        {

            int a = 0;

            int b = 1;

            int c = b / a;

        }

IMPORTANT:

For an application that includes managed code, the common language runtime will present a dialog to JIT-attach a debugger.  We don't want this in our unattended scenario as it will prevent us from being able to spin up a new process. The registry key that controls this option is called:

HKEY_LOCAL_MACHINE\Software\Microsoft\.NETFramework\DbgJITDebugLaunchSetting.

If value = 0, prompt the user by means of a message box. The choices are:

Continue. This results in a stack dump and process termination.

Attach a debugger. In this case, the runtime spawns the debugger listed in the DbgManagedDebugger registry key. If none, control is returned, and the process is terminated.

If value = 1, simply return control. This results in a stack dump, after which the process is terminated.

(No more dialog)

If value = 2, spawn the debugger listed in the DbgManagedDebugger registry key.

Option 2: If you want to disable the JIT debug dialog, but still want an error dialog:

Visual Studio.NET|Tools|Options|Debugging|Just-In-Time and deselect "Common Language Runtime"

and now you’ll get an OK/Cancel dialog instead of the select a Debugger Dialog.

Note: The registry entry from Option 1 above will need to be 0 for the dialog to show up. Here, we need to set this value to "1"- so no more dialog! What will happen now is that we get our logging, the new app instance is spun up, and the old quits with no dialog.

There is one other little item I've added to the sample app. If you look in the assemblyinfo.cs file, you'll see this:

[assembly: AssemblyTitle("UnhandledExceptionRestart")]
#if(DEBUG)
[assembly: AssemblyDescription("[DEBUG]")]
#else
[assembly: AssemblyDescription("[RELEASE]")]
#endif

I've started to do this with all my builds. Now, you can just right click on the assembly in Windows Explorer and look at the summary, and you'll see either "RELEASE" or "DEBUG". You can also retrieve these properties programmatically through reflection, and have your app be able to "self report" on itself and all the assemblies it uses. If you want to get really creative, you could even add this information to the assemblyinfo VisualStudio project template file, and have it be there automatically for all your new assemblies.

As Top Ramen would say, "Now, that's using your noodle!". If your app blows up, wouldn't it be nice to at least get an idea of where and what happened? That's what this will do - with a nice option to restart the app.



Download the Solution zip File

By Peter Bromberg   Popularity  (2830 Views)