Assumption: This write-up assumes that you are familiar with the managed exception handling constructs (e.g. catch, filter, fault, finally). If not, you may want to read this and also refer to the CLI specification.

 

Managed exception handling exposes constructs to handle an exception (e.g. catch and filter blocks) and also to perform any cleanup required by the application, in case the control flow left exceptionally using either the fault or finally blocks. The subtle difference between the latter two is that finally blocks are executed for both exceptional and non-exceptional execution paths, while fault blocks are executed only during an exceptional execution path. Both, however, are executed in the unwind pass of exception handling.

 

Cleanup using finally blocks (or how finally blocks can be made to behave like fault blocks)

 

A common cleanup pattern involving finally blocks is typically implemented for resource cleanup, as shown below:

 

class Program

    {

        static void Main(string[] args)

        {

            FileStream fs = null;

            bool fDidWorkComplete = false;

 

            try

            {

                DoWork(ref fs, ref fDidWorkComplete);

            }

            catch (ArgumentException ex)

            {

                Console.WriteLine("Caught: {0}", ex.ToString());

            }

        }

 

        private static void DoWork(ref FileStream fs, ref bool fDidWorkComplete)

        {

            try

            {

                try

                {

                    fs = File.Create("d:\\kgk\\Log.txt");

 

                    // Do something that will generate an exception

                    Console.Write("Writing {0} to the file", GetString().ToLower());

 

                    // Write string to the file here

 

                    // If we are here, our work is done. Close the file

                    // and set the flag.

                    fs.Close();

 

                    // Flag that we completed the work we wanted to do.

                    fDidWorkComplete = true;

                }

                finally

                {

                    if (!fDidWorkComplete)

                    {

                        // Close the file if required

                        if (fs != null)

                        {

                            WrapperToCloseFile(fs);

                        }

 

                        // Reset other application state

                    }

                }

            }

            catch (NullReferenceException ex)

            {

                Console.WriteLine("Caught {0}", ex.ToString());

            }

        }

 

        private static void WrapperToCloseFile(FileStream fs)

        {

            // Do something that may generate an exception

            throw new ArgumentException("Exception generated by WrapperToCloseFile");

            fs.Close();

        }

 

        private static string GetString()

        {

            return null;

        }

    }

 

The key here is the flag fDidWorkComplete. For non-exceptional case, we will be able to finish our work, release any resources (e.g. closing the file stream) and set this flag. Since finally blocks are executed for non-exceptional case as well, the code in finally block checks if fDidWorkComplete was set or not. If an exception occurs before the flag was set, finally block will be invoked (by the CLR's EH system) provided someone caught the exception and hence, cleanup will be performed. For the non-exceptional case, the execution will fall through in the finally block but won't do cleanup since the flag is set.

 

Effectively, the snippet above shows how fault blocks can be emulated using finally to perform cleanup during an exceptional case using flags (like fDidWorkComplete) to determine whether certain cleanup code should be executed or not. This enables you to get the semantics of fault blocks in a language—such as C#--that doesn’t support them.

 

Exceptions and invocation of fault/finally blocks under exceptional cases

 

Code in fault/finally block needs to be very careful about performing the cleanup and should not introduce another unexpected exception. The fact that your code has a fault/finally blocks to be executed under exceptional cases imply that you anticipate that an exception can occur in the corresponding try block and if one does and is caught, you also know how to reset the program state and release resources. The importance of this cleanup cannot be emphasized enough- the finally/fault blocks are invoked as part of handling the exception to help reset the application state.

 

Hence, if another exception is thrown from within the finally/fault block, not only the cleanup for the original exception will be left incomplete leaving the application in an inconsistent state, but if it escapes out of the block, it will also override the original exception, leaving you with no idea of what was the original exception! Executing the managed code snippet above, we get the following output:

 

Caught: System.ArgumentException: Exception generated by WrapperToCloseFile

   at Code.Program.WrapperToCloseFile(FileStream fs) in D:\Office\Docs\BlogPosts\BackoutCodeAndExceptions\Code\Code\Program.cs:line 69

   at Code.Program.DoWork(FileStream& fs, Boolean& fDidWorkComplete) in D:\Office\Docs\BlogPosts\BackoutCodeAndExceptions\Code\Code\Program.cs:line 53

   at Code.Program.Main(String[] args) in D:\Office\Docs\BlogPosts\BackoutCodeAndExceptions\Code\Code\Program.cs:line 18

 

As can be seen, we don’t know that the original exception generated was NullReferenceException by DoWork. At such a point:

 

1.       Your application state is inconsistent since the cleanup and handling of the first exception never executed to completion

2.       Due to an exception out of the finally/fault blocks while attempting to handle the original exception, you have also lost the original exception context and don’t know what really went wrong.

 

At this point, it should seem obvious that continuing to execute an application in such an inconsistent state is definitely not the best thing to be done but will only result in more consequential bugs that one may end up investigating (and may not even point to the cause of the original problem).

 

Is this discussion valid for managed code only?

 

Actually not! CRT (the C runtime library) considers exceptions escaping out of destructors fatal enough to terminate the process. Below is the C++ equivalent of the managed code snippet above:

 

#include "stdafx.h"

#include <stdio.h>

 

class CTestException

{

};

 

class CDifferentException

{

};

 

class CTest

{

      bool fDidWork;

 

      void FreeResources()

      {

            printf("Free resources is throwing an exception.\n");

            throw new CDifferentException();

      }

 

public:

      CTest() { printf("CTest ctor\n"); fDidWork = false; }

      void DoWork() {

 

            printf("DoWork is throwing an exception.\n");

            // throw an exception

            throw new CTestException();

 

      }

 

      ~CTest()

      {

            if (!fDidWork)

            {

                  printf("~CTest doing cleanup.\n");

                  FreeResources();

            }

      }

};

 

void DoWorkWrapperInner()

{

      CTest testObj;

 

      testObj.DoWork();

}

 

void DoWorkWrapper()

{

      try

      {

            DoWorkWrapperInner();

      }

      catch(CTestException *pTestException)

      {

            printf("Caught CTestException exception.\n");

      }

}

 

int _tmain(int argc, _TCHAR* argv[])

{

     

      try

      {

            DoWorkWrapper();

      }

      catch(CDifferentException *pException)

      {

            printf("Caught CDifferentException exception.\n");

      }

 

      return 0;

}

 

CTest::DoWork throws a CTestException that is caught in DoWorkWrapper, resulting in an unwind that triggers the CTest destructor (in DoWorkWrapperInner) that throws a CDifferentException in FreeResources. CDifferentException escapes out of the destructor and this results in CRT proceeding to terminate the process:

 

 

This can also be confirmed by viewing the stack at the time of process termination:

 

# ChildEBP RetAddr 

00 001fe0b8 5fd7e9ad MSVCR90D!_NMSG_WRITE+0x75 [f:\dd\vctools\crt_bld\self_x86\crt\src\crt0msg.c @ 198]

01 001fe3f4 5fd502ee MSVCR90D!abort+0x2d [f:\dd\vctools\crt_bld\self_x86\crt\src\abort.c @ 59]

02 001fe428 5fd519e0 MSVCR90D!terminate+0x6e [f:\dd\vctools\crt_bld\self_x86\crt\prebuild\eh\hooks.cpp @ 130]

03 001fe43c 5fd51b26 MSVCR90D!__FrameUnwindFilter+0x40 [f:\dd\vctools\crt_bld\self_x86\crt\prebuild\eh\frame.cpp @ 1070]

04 001fe444 5fd79bd4 MSVCR90D!__FrameUnwindToState+0x106 [f:\dd\vctools\crt_bld\self_x86\crt\prebuild\eh\frame.cpp @ 1153]

05 001fe458 5fd7cb3c MSVCR90D!_EH4_CallFilterFunc+0x12

06 001fe490 5fd732a4 MSVCR90D!_except_handler4_common+0xbc

07 001fe4b0 77da5809 MSVCR90D!_except_handler4+0x24

08 001fe4d4 77da57db ntdll!ExecuteHandler2+0x26

09 001fe584 77da5667 ntdll!ExecuteHandler+0x24

0a 001fe584 7600ae33 ntdll!KiUserExceptionDispatcher+0xf

0b 001fe8f0 5fd4f8f2 KERNELBASE!RaiseException+0x58

0c 001fe930 012c17df MSVCR90D!_CxxThrowException+0x52 [f:\dd\vctools\crt_bld\self_x86\crt\prebuild\eh\throw.cpp @ 161]

0d 001fea40 012c16fc ExceptionInCPPDtor!CTest::FreeResources+0xaf [d:\kgk\development\blog\code\exceptionincppdtor\exceptionincppdtor\exceptionincppdtor.cpp @ 23]

0e 001feb20 5fd53047 ExceptionInCPPDtor!CTest::~CTest+0x4c [d:\kgk\development\blog\code\exceptionincppdtor\exceptionincppdtor\exceptionincppdtor.cpp @ 42]

0f 001ff780 012c187d MSVCR90D!_NLG_Return [f:\dd\vctools\crt_bld\SELF_X86\crt\prebuild\eh\i386\lowhelpr.asm @ 73]

10 001ff874 012c198d ExceptionInCPPDtor!DoWorkWrapper+0x4d [d:\kgk\development\blog\code\exceptionincppdtor\exceptionincppdtor\exceptionincppdtor.cpp @ 57]

11 001ff968 012c20c8 ExceptionInCPPDtor!wmain+0x4d [d:\kgk\development\blog\code\exceptionincppdtor\exceptionincppdtor\exceptionincppdtor.cpp @ 70]

12 001ff9b8 012c1f0f ExceptionInCPPDtor!__tmainCRTStartup+0x1a8 [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 579]

13 001ff9c0 775f36d6 ExceptionInCPPDtor!wmainCRTStartup+0xf [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 399]

14 001ff9cc 77d8883c kernel32!BaseThreadInitThunk+0xe

15 001ffa0c 77d8880f ntdll!__RtlUserThreadStart+0x70

16 001ffa24 00000000 ntdll!_RtlUserThreadStart+0x1b

To conclude: Generating a new exception from within fault/finally blocks, as part of handling an existing exception, will leave your application state inconsistent, and letting it escape out of the termination handler is no less than a semantically fatal program error.

 

 

- Gaurav Khanna,

Developer, CLR.