Home > Uncategorized > Exceptions Part 3: Why do we need ‘finally’ blocks?

Exceptions Part 3: Why do we need ‘finally’ blocks?

See the Contents Page for this series of articles

C# programs compile to an intermediate language (IL). If you want to dig deeper into exception handling, IL doesn’t seem very enlightening at first, because it also has the keywords try and catch. It’s like opening a TV and inside it… there’s a little TV. That’s no kind of explanation!

But don’t be discouraged. We can learn something from the IL exception system by considering what C# doesn’t expose to us. For example, in IL there’s something called a fault block. If it were exposed directly in C#, we might use it like this:

int originalFileLength = GetLengthOfFile();

try
{
    TryAppendingToFile();
}
fault
{
    SetLengthOfFile(originalFileLength);
}

The fault block only runs if an exception is thrown, and doesn’t stop the exception from travelling further up the stack. We don’t want to stop an exception in its tracks, but we don’t want to leave anything half-changed. So we shrink the file back to its original length and let the exception carry on past us.

But we can actually do this with another feature of IL exceptions, the finally block. We just need a bool variable to help with the logic:

int originalFileLength = GetLengthOfFile();
bool finished = false;

try
{
    TryAppendingToFile();
    finished = true;
}
finally
{
    if (!finished)
        SetLengthOfFile(originalFileLength);
}

And so, in the interests of clogging up the language with unnecessary features, it makes a lot of sense that fault is not included as a special feature. Buoyed by this success, we push further and ask: why can’t we get rid of finally as well? Here’s the canonical example:

try
{
  AttemptSomething();
}
finally
{
  CleanupWhateverHappens();
}

The finally block always runs, however we leave the try block, and also doesn’t interfere with the movement of the exception up the stack.

Surely we just need:

try
{
  AttemptSomething();
  CleanupWhateverHappens();
}
catch
{
  CleanupWhateverHappens();
  throw;
}

If AttemptSomething throws, it will be caught and we will clean up and then rethrow, allowing the exception to continue unimpeded. If it doesn’t throw, we’ll clean up anyway. Seems perfect!

But this is wrong. The above two snippets mean very different things. They behave differently. In catching and re-throwing, we make the mistake of thinking that we are “exception-transparent” – exceptions pass through us harmlessly without any side-effects. But that isn’t so. It is true for finally, but not for catch.

It all has to do with those unfortunate – but sadly common – situations where our programs throw exceptions that we aren’t expecting at all. Recall that last time we concluded that we need to be able to not catch some exceptions, because they tell us our program is in an unknown state and it’s time to quit before we can do more serious damage.

To emphasise this point, it’s time for me to introduce the Sergio Leone classification of the results that can arise from executing some part of a program:

  • Good – the operation completes without any exception being thrown.
  • Bad – an exception is thrown during the operation, but the program is designed to deal with it; it maintains a known state, catches the exception and performs some alternative operation so it can keep going.
  • Ugly – an exception is thrown that you never thought would be. As a result, the state of the program is unknown. Absolutely anything could happen next. Certainly there can be no question of restoring a known state – how can you make the necessary changes to get to some state if you don’t know what the current state is?

As a sidebar, if we’re being really thorough we can further classify Ugly situations. The most Ugly of all occurs when we are calling into native code, and it contains a memory corruption bug. It can therefore corrupt the internals of the CLR! Clearly at that point the entire process may as well give up the ghost – nothing is reliable. A little less Ugly is the case where the state of program variables in the CLR is inconsistent with expectations, the most familiar example being a reference that mysteriously has the value null when it should be referring to an object; if the CLR is sound, then it may be that the process can survive by unloading and reloading the affected AppDomain so as to pretend nothing happened, but in practise that isn’t much different from tearing down the whole process.

So either way, if an Ugly exception is thrown, it’s probably better on the whole if we stop running as soon as possible. If the thing I just attempted went wrong in some way I failed to anticipate, how can I have any confidence that the next thing I do will work?

In fact it’s especially likely to fail, because ironically the next thing I’ll probably try to do is clean up the mess, just as shown in the finally examples above. This will undoubtedly involve messing with the same data structures and resources that only just blew up on me.

And then consider the fact that if you have a bug, you probably want to know exactly what state things were at the moment you detected it. For example, normally it’s vitally important to clean up temporary files, but if a bug arises during the use of a temporary file, then it might be really useful to be able to examine the contents of the file. It’s not actually helpful if that file gets silently deleted by some clean-up code. And that’s on top of the fact that – as your program is in an unknown state – it might delete the wrong file anyway.

There is only one sane answer: if things get Ugly, stop the program running. The only question is, how does a program reach a decision about whether an exception is Bad or Ugly?

In an environment with exception handling, the decision is taken in a kind of democratic way. The stack consists of “frames” containing local variables and parameters, and there may be several frames in which there are try/catch statements. You can think of each frame on the stack as having the power of veto – any single frame can step up and demand that the program must keep running, overriding the default behaviour of crashing to a halt.

The innermost catch statement that matches the exception type will catch it. If none of them catch it, then the exception halts the program. So a caught exception corresponds to a Bad situation, and an uncaught one means things are officially Ugly.

And as we said above, when things are Ugly, we usually need to stop our code running. However, as everyone knows, finally blocks always run. So as an uncaught exception flies up the stack, won’t it trigger the execution of all the finally blocks it encounters along the way?

Not necessarily!

It’s possible to stop finally blocks running – but only for uncaught exceptions. The CLR searches up the stack to see if the exception is going to be caught, before it starts running any finally blocks. So it gets the final decision on the Bad vs. Ugly question first. This is an awesomely important feature of exceptions in the CLR, crucial to our ability to really stop our programs running when things get Ugly.

This is the real reason why it is vitally important to not catch an exception unless you intend for your program to continue running. Catching and rethrowing is not exception-transparent. It tells the CLR that you believe the situation is Bad, not Ugly, and so it has the side-effect of forcing some of your finally blocks to execute.

As the most extreme counter example, you shouldn’t do this:

static void Main(string[] args)
{
    try
    {
        // attempt everything...
    }
    catch (Exception x)
    {
        Console.WriteLine(x.Message);
    }
}

By wrapping your entire program in a catch-all, you are guaranteed that finally blocks will run in response to all exceptions, including exceptions you’ve never heard of, which will probably be thrown in situations where those finally blocks are highly likely to make the existing mess even worse.

So the true value of a finally block is not that it always runs. A language with finally blocks that always run must be judged to have a serious design flaw!

A useful finally block only runs in Good and Bad situations, yet not in Ugly situations.

Unfortunately, the default behaviour in the CLR is to run finally blocks even after an uncaught exception is detected, and many people don’t realise the disadvantages of this.

But the good news is that we can easily override the default behaviour. This means that CLR-based languages have a big advantage over JVM-based languages and C++, as we will see next time.

Next time: What’s it like to use languages where you aren’t allowed to stop finally blocks running?

Advertisements
  1. jalf
    June 29, 2010 at 10:02 pm

    I see your point about `finally` clauses always running, but isn’t that mitigated in C++ by the fact that exceptions generally don’t cover the main “ugly” situations in the first place? Access violations and that sort of fun isn’t caught by the C++ exception mechanisms, so no cleanup will be attempted in those obviously “ugly” cases. (And because cleanup code is generally concentrated in per-class destructors, rather than `finally` blocks, it’s generally applied to local chunks of state that are not (in theory) affected by the error that caused the exception)

  2. ficedula
    June 29, 2010 at 11:05 pm

    @jalf: Not sure it totally mitigates it … reduces the number of cases where you are able to attempt and cope with it, perhaps, but those cases that remain are still absolutely valid.

    Consider the case where you construct an object A which internally uses some third-party code; then call a method on A which unexpectedly throws XmlParserException (and you didn’t even know it was using an XML parser, but whatever!). Is it safe to clean up just by letting the destructors on your stack objects run…? Probably not: the first destructor to be called will probably be A’s destructor, and that means all the nasty things mentioned above: a clean up attempt on an object whose data is in an inconsistent state, which may corrupt logs and/or conceal the original error. Or if you’re really unlucky, call through an invalid method pointer; let’s hope it doesn’t end up pointing to HardDisk.Format() by accident…

    Your cleanup code being in a destructor rather than a finally block doesn’t really help you; far from being applied to chunks of state that aren’t affected by the error, it’s very likely that the first destructor to run will be *exactly* the one operating on a chunk of state affected by the error!

    Of course, if you know for sure that the object can be cleaned up without adverse consequences and it won’t have modified global state in any way that will cause future problems – sure, you can clean up and run the destructors. But if you know that, then it isn’t an Ugly exception after all, it’s just Bad! But if you receive an exception type you haven’t analysed and know the exact consequences of, you should assume the worst: this object is #$*(#ed and even calling its destructor could corrupt memory even more, so we should not attempt to clean up.

  3. earwicker
    June 30, 2010 at 9:05 am

    @ficedula – absolutely, pretty much what I was going to say.

    It is true that we can identify some circumstances that are decidedly Ugly, regardless of context (dereferencing a null being the obvious example), and so therefore it is indeed A Good Thing that we cannot catch them in C++ as exceptions in most implementations these days. These situations often correspond to the dreaded “undefined behaviour”.

    But this doesn’t mean those are the only Ugly situations. If my program’s state, as defined by my design, gets into a state that I assert it should be impossible to get into, then things are most certainly Ugly. And yet by the language definition, whatever happens next is fully determined. It just isn’t known to me.

    It’s traditional with undefined behaviour to humorously warn the novice programmer that “anything could happen – the missiles could be launched!” But in fact in the C++ runtime model, the degrees of freedom are so great, the entropy is so high, that the majority of undefined behaviour all looks pretty similar (just as any two rooms full of gas particles look pretty similar). Typically at some point you get a hard crash due to a dangling pointer. The chances of something more exciting happening first are quite low in my experience.

    Whereas a program in an unknown state that still has defined behaviour according to the language definition is perhaps not going to give you the benefit of a hard crash. It is going to keep doing what it is defined to do! Which is highly likely to be something you don’t want it to do. C++ is as susceptible to this as any language. This is the kind of situation where missiles get launched.

    As I mention above, the basic feature of exceptions that differentiates them from hard crashes is that every stack frame has the power of veto over the halting of the program. You could argue that violated assertions of a class invariant should not cause an exception to be thrown – instead they should halt the program, just like a hard crash (this is a common implementation of the ‘assert’ macro in debug builds). But that is like saying “We don’t have a problem with exceptions, because we don’t use them.” Clearly in cases where exceptions are avoided altogether, this series of articles will be irrelevant… 🙂

  4. ficedula
    June 30, 2010 at 1:49 pm

    Another thing I just thought of is that in unmanaged languages such as C++, an unexpected error, indicating your program invariants have been violated, is possibly an indication of a more serious problem.

    If your class finds that a pointer is null, and it should never be null (initialised in constructor to non-null, never changed to a null value after that point … yet somehow in this method call, it is null!) there’s two obvious possibilities;

    1) A logic bug; while you meant to never set it to null, you missed a check in some corner case.

    2) Somebody has been scribbling all over your memory with an invalid pointer…!

    So while C++ won’t stop you using an invalid pointer, finding your invariants to no longer hold is potential evidence that it has been happening – which only reinforces the decision to bail out and terminate.

    Not sure if it means anything much beyond that though.

  1. June 28, 2010 at 1:06 pm
  2. July 2, 2010 at 11:42 pm
  3. July 24, 2010 at 6:22 pm
  4. July 24, 2010 at 6:30 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: