Exceptional Safety

Delphi is exception safe language. That means well written code can recover from the most horrible exceptions, including the dreaded out of memory error, and continue running in a perfectly operational state - as if nothing bad happened. Of course, that is a feature that Delphi provides, but your code and application eventually must determine logic at which point raised exception is recoverable and at which point it is not.

For instance, if you allow user to open some external file for further processing and your code trips on out of memory exception because the file is too large to process, you can fully recover from such event, show error message to the user "Sorry, selected file is too big" and application can merrily continue working. On the other hand if you trigger out of memory error during some operation your application absolutely must be able to perform, you can decide that the best course of action is terminating the application - after you show appropriate error message and perform whatever shutdown procedure is required.

One little thingy

Focus of this post is not teaching you how to write exception cleaning and handling code and how and when to use try...finally and try...except blocks.

It is about pointing to one often forgotten and ignored fact that can crush all your exception handling efforts: object destruction process must not cause or raise any unhandled exceptions or you will have memory leaks beyond your ability to fix them.

Particulary, that means the BeforeDestruction method - and destructors themselves - must never ever allow exceptions to escape them. Any escaping exception there will always cause memory leak. Period.

Where does it leak?

Unhandled exception in any destructor will not only break calling inherited destructors chain (if there are any), but more importantly it will skip calling FreeInstance method that is automatically called after the destructor chain and is responsible for cleaning up instance managed fields and releasing its memory allocated on the heap. If FreeInstance does not run, your code will leak that object instance's memory.

Same applies to the BeforeDestruction method - unhandled exception there will skip calling the whole destructor chain as well as FreeInstance.

Proper handling of any exceptions inside BeforeDestruction method or destructors implies that you must make sure that all code that is responsible for any kind of cleanup, including calling inherited methods, that absolutely must be executed is executed during that exception handling process.

But, I have try...finally

No amount of dancing around broken destructors (or BeforeDestruction) will fix the memory leak. That is just wishfull thinking. The only way to fully and properly solve such leaks is to fix broken destructors (or BeforeDestruction) from within.

Even code using interfaces and automatic memory management will cause memory leaks if exceptions break destruction process.

Assuming that following classes cause unhandled exceptions in BeforeDestruction or in any of the destructors all following code will result with memory leaks.
var Broken: TBroken; begin Broken := TBroken.Create; try finally Broken.Free; end; end;
var Broken: IBroken; begin Broken := TBrokenAutomatic.Create; end;

Please, don't

If you are tempted to go around your code eating up all exceptions with try...except blocks in every destructor you have ever written, then please don't.

Not every single line of code is exception prone. You just have to protect code that really can cause an exception and not all of it.

Following destructor code is perfectly fine. No need to handle any exceptions there.
destructor TFoo.Destroy; begin FBar.Free; inherited; end;

Of course, if FBar destructor is broken then TFoo destructor will also be broken, but the proper place to handle potential exception is in TBar destructor itself. Adding try...except code around FBar.Free will accomplish absolutely nothing because if TBar destructor is broken, TBar object instance will leak regardless. Yes, eating up exception in TFoo destructor would at least prevent leaking of TFoo instance, but if it is broken does it really matter how much?

Comments

Popular posts from this blog

Coming in Delphi 12: Disabled Floating-Point Exceptions

Beware of loops and tasks

Catch Me If You Can - Part II