Friday, November 3, 2006

try...finally.

There were quite a few interesting comments made to this post from the other day that seemed to indicate that there is a little confusion out there regarding exceptions.  More specifically, the try...finally construct.

I've seen some rather interesting twists on the use of try...finally that have made me pause and wonder why it was done that way.  When programming with exceptions, you always have to be aware that at nearly any point and without warning, control could be whisked away to some far-off place.  In many ways you have to approach and think about the problem very similarly to how you would in a multi-threaded scenario.  In either case, you always have to keep in the back of your mind two (or more in the multi-threaded case) potential code paths.  The first one is easy since that is the order in which you're writing the code statements that define the overall logic and intent of your program.  The other, and often forgotten code path is the exception execution path.

When an exception is raised (or "thrown" in the parlance of C++ and other similar languages) a lot of behind the scenes work is set into motion.  I won't be going into the details of exactly what is going on since that tends to be platform and language dependant.  I'll focus more on what happens from the programmers perspective and even more specifically the try...finally construct.  One way to think of the try...finally block is that it is the programmer's way of "getting in the way" of that secondary code execution path.  However, it is also unique in that it also "gets in the way" of the normal code execution path.  In the grand scheme of things, the try...finally block is one of the most used (and often mis-used) block types for programming with exceptions.  I'd venture to say that in the typical application the ratio of try...finally blocks to try...except blocks is on the order of 100:1.  But why use them at all and what are they good for?  It's all about resource management.  Memory, files, handles, locks, etc... are all examples of the various resources your application uses and interacts with.  The whole point of the try...finally block is to ensure an acquired resource is also properly handed back regardless of which execution path the application takes.

Let's look at some common misuses of the try...finally block and examine them more closely.

var
Obj: TMyClass;
begin
try
Obj := TMyClass.Create;
...
finally
Obj.Free;
end;
end;

There's a subtle problem here... Let's follow the normal execution flow first.  Control enters the try block, an instance of TMyClass is allocated and the constructor is called, control returns and the local variable, Obj, is assigned.  Some operations are done with the Obj instance (the "..."), then control enters the finally block and the Obj instance is freed.  So far so good, right?  I mean, the memory is allocated and freed and all is well with the heap along with any other resources needed by the TMyClass instance (assuming it is a properly designed class, that is).


Now let's look at the other possible flow of control.  Control enters the try block, an instance of TMyClass is allocated and the constructor is called.  Here is where something can go horribly wrong.  When programming with exceptions, the possibilities are nearly endless as to what can happen during the call to the constructor.  The most obvious is the case where the memory manager is unable to allocate enough space on the heap to hold the instance data for the new instance.   An "Out of Memory" exception is raised.  Hey, but that's OK because the finally block will get in the way of the the exception control flow, right?  Yep, that's right.  So memory was never allocated, the constructor was never called, local variable Obj was never assigned, and control is passed to the finally block which contains.... uh... Obj.Free;  Do you see it now?  Yep, that's right, the Obj reference was never properly set.  Because of that, the call to Obj.Free; is not good for the health of your application.  Chances are that another exception is going to be raised which will supercede the original exception (most likely a more fatal and nasty one).


So how do we fix this?  Hey I know!  What if we just made sure to pre-initialize the Obj reference to nil (Obj := nil;)?  Sure.  You could do that, but that is just adds another line of code to your function.  How can we arrange the above code to ensure that every time control is passed to the finally block regardless of which path of execution is used to get there?  It's actually very simple.  Here's the same block with that subtle change:

var
Obj: TMyClass;
begin
Obj := TMyClass.Create;
try
...
finally
Obj.Free;
end;
end;


But now the construction of the TMyClass instance isn't protected!  It doesn't have to be and let's examine why.  From some of my previous posts regarding exception safety and the use of Assigned, we alluded to the fact that while the constructor of an object is executing, if any exception is raised the destructor will automatically be called, the object will be freed and the exception is allowed to continue on its merry way.  There are essentially two main areas were things can go wrong.  The first is during the actual allocation of the memory for the instance.  If the memory manager is unable to find a block of free memory large enough to hold that instance, it will raise an "Out of Memory" exception.  Since the instance was never actually allocated, there is no need to ever execute the Obj.Free; line of code.  Since the try...finally block was never entered, Obj.Free will never be called.  The other place where things can go wrong is in the object's constructor.  In this case the memory manager allocated the memory and then control was passed off to the the constructor that would begin to setup the instance.   If something fatal happened along the way there, we know from those past articles that the destructor will automatically be called and the instance memory handed back to the memory manager for later re-use.  Since the object was already freed and, again, control never entered the try...finally block, the Obj.Free; line is never executed.  So in both of those scenarios, there was no need to touch the local Obj variable reference since the resources associated with the TMyClass instance were taken care of.  If the memory allocation succeeds, the constructor runs to completion, then control will return and the local variable Obj will be assigned after which control will then enter the try...finally block.  It is only at this point that you always want to make sure the TMyClass instance referenced by Obj is freed regardless of what happens next.


There are a lot of other interesting "twists" on the usage of try...finally and try...except blocks that I've seen over the years.  The above case stands out in my mind as the most common mistake made.  I'll address some of the other cases in following posts.  If you have any questions about certain common idioms and patterns that you're not sure are exception aware, post some comments and maybe I'll address them in future posts.