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 except开发者_开发技巧ions (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!
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
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 this setting will save you heartache.
UnhandledExceptionMode
-- which I expect you probably will if you're running this code yourself --
The Testing
Inside the debugger
Throw in the UI thread
- Click Throw in UI
- Get "unhandled exception" helper in the debugger
F5
to continue execution- Dialog will show, indicating that the Application handler received an exception event from the UI thread
- Click OK
- Application does not crash, so feel free to rinse and repeat.
Throw in a background thread
- Click Throw in background
- Dialog will show, indicating that the AppDomain handler received an exception event from a background thread
- Get "unhandled exception" helper in the debugger
F5
to continue executiongoto 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.
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.
- Click Throw in background
- Dialog will show, indicating that the AppDomain handler received an exception event from a background thread
- Get Windows crash dialog
- Click
Debug
to snatch the exception with JIT debugging - Get "unhandled exception" helper
F5
to continue- 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
It's just a VS option that is enabled by default (only relevant when debugging of course). So no need to detach the debugger if all you want is to (test?) crash and burn where your un-debugged process will.
Hard to tell without knowing what the code for the original throw is doing. It is possible that there are outstanding forms events that get processed as part of the app shutdown that ends up re-invoking the original code fragment. It could also be a virtual function on your application class that gets invoked during shutdown.
You should be able to just print the stack trace from your catch handler to figure it out though.
I suspect that in ExceptionHandler.OnUnhandledException
a new exception might be raised which remains unhandled, gets bubbled up to the unhandled exceptions handler, which raises your ExceptionHandler.OnUnhandledException
method and so on.. (infinite loop -> stack overflow).
So doublecheck your OnUnhandledException method for thrown errors.
Edit: funny, I try to reproduce your error, but instead of throwing an error again and again, my app. simply exits after the unhandled exception handler method, no matter what happens in there. Were you debugging or running? It might cause a difference.
精彩评论