Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exception handling loop puzzle

I recently encountered a behavior that I've never seen before. I cannot quite understand what's going on most likely due to lack of fundamental knowledge with regards to the inner workings Exception Handling - or maybe I am just missing something obvious.

I recently added exception handling to an app as a sort of fallback in case of unhandled exceptions. I am basically handling ThreadException and UnhandledException as shown below:

// Add the event handler for handling UI thread exceptions to the event.
Application.ThreadException += new ThreadExceptionEventHandler(ExceptionHandler.OnUIThreadException);

// Set the unhandled exception mode to force all Windows Forms errors to go through
// our handler.
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

// Add the event handler for handling non-UI thread exceptions to the event. 
AppDomain.CurrentDomain.UnhandledException +=
    new UnhandledExceptionEventHandler(ExceptionHandler.OnUnhandledException);

// Runs the application.
Application.Run(new ErrorHandlerForm());

Some other piece of code I had in the app was already catching exceptions - and since I didn't have exception handling in place I was just rethrowing the exception to make sure it was not swallowed:

//code in some method of the Program
try
{
   foo.SomeFooCall();
}
catch(Exception ex)
{
  logger.Log(ex.Message);
  // I don't swallow!
  throw;
}

Once I had the exception handling in place (which is also logging) I should've removed that try catch block above - but I forgot to do so and I am experiencing a strange behavior which is the subject of this question.

When and exception is thrown somewhere inside the foo call, it is obviously caught by the code above, logged then thrown again. At this point ExceptionHandling kicks in, does some logging and notification (a simple messagebox) then goes Application.Exit(). What happens next is that the app will go back on the same throw which will trigger error handling whit the same results, and this will go on a number of times till it crashes presumably 'cause the stack trace is full or it somehow detects the infinite loop.

EDIT: The above is in debug mode - if I just run it it'll handle the exception once (show the messagebox, log etc.), then it'll just crash (I am guessing for stack overflow).

I anticipate the answer to this might be trivial (or I may be missing something obvious) - but any pointers/explanations will be highly appreciated.

EDIT: The exception handlers methods take both call down to an OnException method that goes something like:

private void OnUIThreadException(object sender, ThreadExceptionEventArgs e)
{
   OnException(e.Exception);
}

private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
   OnException((Exception)e.ExceptionObject);
}

private void OnException(Exception exception)
{
   MessageBox.Show("Fatal Exception: " + exception.Message);

   logger.Log(LoggingLevel.FATAL, "myLousyApp", exception.Message);

   Application.Exit();
}

I am actually doing smt more than just that - such as asking the user if they want to restart the app and if so restarting it with process id as cmd arg so that when it restarts it'll wait for the old process to exit (it's protected from duplicates instances through a mutex). But for this question this is irrelevant since I am not restarting the app when I experience the behavior described.

EDIT: I created another simple app to reproduce this conditions - I have a simple component that throws exceptions (I am throwing an arbitrary number of exception in a loop), but in all my tests on Application.Exit the app just shuts down nicely and I can't reproduce it. Puzzled with regards to what I should be looking for!

like image 569
JohnIdol Avatar asked Jan 15 '10 15:01

JohnIdol


1 Answers

tl;dr: It's the debugger. Detach and you won't get this weird behaviour.


Alright. I did some experimentation with a Brand Spankin' New Project, and came up with a result. I'll start by posting the code so that you, too, can join in the fun and see it firsthand.

Teh Codez

plz email them to me (unrelated)

Form1.cs

Example form
You will need two buttons on the form. Caption them appropriately so that it's blindingly obvious what you're doing.

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

    private void button1_Click(object sender, EventArgs e)
    {
        throw new InvalidOperationException("Exception thrown from UI thread");
    }

    private void button2_Click(object sender, EventArgs e)
    {
        new Thread(new ThreadStart(ThrowThreadStart)).Start();
    }

    private static void ThrowThreadStart()
    {
        throw new InvalidOperationException("Exception thrown from background thread");
    }
}

Program.cs

static class Program
{
    [STAThread]
    static void Main()
    {
        AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
        Application.ThreadException += Application_ThreadException;
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException, false);

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

    static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
    {
        if (e.Exception != null)
        {
            MessageBox.Show(string.Format("+++ Application.ThreadException: {0}", e.Exception.Message));
        }
        else
        {
            MessageBox.Show("Thread exception event fired, but object was not an exception");
        }
    }

    static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Exception ex = e.ExceptionObject as Exception;

        if (ex != null)
        {
            MessageBox.Show(string.Format("*** AppDomain.UnhandledException: {0}", ex.Message));
        }
        else
        {
            MessageBox.Show("Unhandled exception event fired, but object was not an exception");
        }
    }
}

The project file

Disable the hosting process, otherwise the AppDomain (and Forms itself) won't be unloaded between debugging sessions, which will make the line Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException, false); throw an InvalidOperationException if you change the UnhandledExceptionMode argument between runs. EDIT: or at all, if set to CatchException

It's not strictly necessary for this investigation, but if you're going to play around and change the UnhandledExceptionMode -- which I expect you probably will if you're running this code yourself -- this setting will save you heartache. Disable the hosting process

The Testing

Inside the debugger

Throw in the UI thread

  1. Click Throw in UI
  2. Get "unhandled exception" helper in the debugger
  3. F5 to continue execution
  4. Dialog will show, indicating that the Application handler received an exception event from the UI thread
  5. Click OK
  6. Application does not crash, so feel free to rinse and repeat.

Throw in a background thread

  1. Click Throw in background
  2. Dialog will show, indicating that the AppDomain handler received an exception event from a background thread
  3. Get "unhandled exception" helper in the debugger
  4. F5 to continue execution
  5. goto 2. Really.

It seems here that the AppDomain handler trumps the debugger for whatever reason. After the AppDomain is done with it, however, the debugger does manage to pick up on the unhandled exception. But what happens next is puzzling: the AppDomain handler gets run again. And again. And again, ad infinitum. Putting a breakpoint in the handler indicates that that isn't recursive (at least, not within .NET) so this probably won't end in a stack overflow. The background thread

Now, let's try it...

Outside the debugger

UI thread

Same procedure, same result -- except of course that the debugger helper was conspicuously absent. The program still didn't crash, because UnhandledExceptionMode.CatchException does what it says it will do, and handles the exception "internally" (within Forms, at least) instead of escalating it to the Feds AppDomain.

Background thread

Now this is interesting.

  1. Click Throw in background
  2. Dialog will show, indicating that the AppDomain handler received an exception event from a background thread
  3. Get Windows crash dialog
    Windows crash dialog
  4. Click Debug to snatch the exception with JIT debugging
  5. Get "unhandled exception" helper
  6. F5 to continue
  7. Program exits

Firstly, the AppDomain doesn't go into loops like it does with the debugger attached, and secondly, attaching the debugger just-in-time from the Windows error dialog does not trigger this weird behaviour.

Conclusion

It seems like the debugger does something weird regarding unhandled exceptions making their way to the AppDomain. I don't know much about how the debugger does its magic, so I won't speculate, but the loop only occurs when the debugger is attached, so if the loop is the problem, detaching is one workaround you could use (perhaps using Debugger.Launch() to give yourself time to reattach later if you require it).

<3

like image 85
Tullo_x86 Avatar answered Oct 24 '22 10:10

Tullo_x86