Exceptional Safety
Dalija Prasnikar - 07/Feb/2018
Dalija Prasnikar - 07/Feb/2018
[SHOWTOGROUPS=4,20]
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.
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.
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?
[/SHOWTOGROUPS]
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?
[/SHOWTOGROUPS]