What is an EndException?

| | Comments (5)

Every once in a while, I run into someone using code like the following:

Sub SomeMethod( foo as Integer )
if foo = 12 then Quit
Exception err as RuntimeException
MsgBox "Exception happened"
End Sub

Did you spot the bug? Hint, it's in the title of the blog post. ;-) The problem with this code is that it's using a catch-all exception handler. Personally, I think this is an abuse of the exception system and should be avoided out of principle. But that issue aside, the trouble with this code is that it's also going to catch "internal" exceptions like the EndException and ThreadEndException. So what are these exceptions and why are they needed?

When you go to terminate your application (or a thread), there's one of two ways it can be done. 1) Nicely. 2) Not nicely. The not nicely way of terminating is to simply halt execution immediately. BAM! Everything goes away. The trouble with this approach is that nothing gets cleaned up. No destructors fire, objects are left hanging, etc. So we do things the nice way.

The exception system fits into this equation because it's the goo that makes everything work. When an exception is unhandled, all of the cleanup still happens for every method in the call stack. This is the same cleanup we want to have happen when the user quits the application. So internally, Quit raises an EndException which is handled at the base of the stack (before the App.UnhandledException filter). So the result is that the stack is unwound, objects are all cleaned up, and the application can terminate gracefully. Very awesome!

However, catch-all exception filters throw a monkey wrench into the problem. You see, in REALbasic (as with every language that uses exceptions), doing this is considered bad form. You should *only* catch the exceptions you can handle. And obviously, you can't handle all exceptions gracefully. But, if you do have this sort of code, then have no fear. Simple re-raise the exception you were handed and life will continue merrily along. So do this:

Sub SomeMethod( foo as Integer )
if foo = 12 then Quit
Exception err as RuntimeException
MsgBox "Exception happened"
raise err
End Sub

That will pass the exception (all exceptions, actually) up the call stack, so the quit can continue as expected. But a better solution is to simply avoid using catch-all blocks all together. That's what App.UnhandledException is used for!

Just to tie everything up nicely, the ThreadEndException operates under the same rules. Think of threads as little mini-applications. They have their own call stacks, and things need to be cleaned up on them when exceptions happen. So calling Thread.Kill causes another internal exception to be thrown so that the thread can be cleaned up properly before going away.

5 Comments

Very good explanation, unfortunately not in the RB-docs ;)
On the other hand it makes me uncertain why on earth are the internal exceptions exposed to the RB-users? Wouldn't it be better to make these exclusive to the RB-program itself?

Because that would be an extraordinary amout of work to work around a design flaw that users should avoid anyway. ;-) I've thought of ways to do it, but it'd be a pretty major rewrite.

Who's design flaw.....?(joke,joke haha)

Using any general purpose exception type is a bad idea. This is frowned upon in Java too. You might as well do this:

Sub SomeMethod( foo as Integer )
if foo = 12 then Quit
Exception err as OhPleaseWorkOhPleaseOhPleaseException
MsgBox "This can't be my fault. Probably something Aaron did."
CrashComputer( doFryPowerSupply )
End Sub

Uh oh, Aaron put the doFryPowerSupply() function back in v2007?!

Leave a comment

Disclaimer

I'm currently an employee of REAL Software. My blog is mine. The opinions represented in this blog are mine as well and may not represent my employer's opinions. All original material is copyrighted and property of the author.

REALbasic® is a registered trademark of REAL Software, Inc. REAL SQL Server™ and Lingua™ are pending trademarks of REAL Software, Inc. All rights reserved.