Exceptions out of Fault/Finally

Exceptions out of Fault/Finally

  • Comments 14

 

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.

Leave a Comment
  • Please add 3 and 8 and type the answer here:
  • Post
  • I probably missed the point (since it appears to be too easy), but a fault block can be written in C# as follows:

    catch

    {

     // fault block

     ...

     // end of fault block

     throw;

    }

  • Pascal: That's not equivalent because it forces unwinding.  Finally blocks only run if some other outer block catches the exception.

  • @Bruno - in a console app running on Windows XP and Windows Vista, an exception that leaves main without being caught will fire the AppDomain's UnhandledException event and then run finally blocks. So you can force sudden death in your UnhandledException handler, but if you don't do that, finally blocks will run. I have heard that Windows 7 will be different: finally blocks will never run for uncaught exceptions, but haven't been able to try it out yet.

  • @Pascal,

    There are several problems with that approach.

    1) Throwing exceptions is slow, even when you are rethrowing them. An exception that has to be caught and rethrown several times on its way up a call stack in order to simulate "fault" behavior will have a significant negative impact on performance.

    2) When debugging, catching an exception unwinds the call stack to that point. When you rethrow the exception, the debugger will break at the point where the exception was rethrown, not where it was thrown originally. All of the context in the part of the stack that was unwound is lot, which makes debugging significantly harder.

  • @Pascal: That is not equivalent to fault block since fault blocks are executed "before" we enter the catch block.

    There is a clear distinction between fault/finally and catch. If fault/finally are present down the stack (stack growing down), even if there is no managed catch to handle the exception but an unwind was triggered(e.g. exception was caught in native code), they will be executed.

  • Looks like it's no different in Windows 7 in the current beta - the uncaught exception triggers Window Error Reporting, and when the user opts to close the application, the finally blocks run. What may have changed is the strict order in which things happen, but you still have to enlist on UnhandledExeption to stop the finally blocks running. I think this is odd, because what if my finally blocks try to delete a temporary file? My program is already buggy enough to crash, should it be allowed to continue running in an unknown state and possibly delete something important? Surely a crash should stop execution dead.

    Maybe Andrew Pardoe would be able to clarify - (apologies for wasting more of his time, but I clearly misunderstood the exact details of this last time).

  • Thank you for submitting this cool story - Trackback from DotNetShoutout

  • @Daniel: Are you talking about doing something like TerminateProcess from within the AppDomain's UnhandledException event to prevent the finally blocks from running?

    Your point about executing finally/faults is interesting. Whenever you write such a block that will execute in the exceptional case, you are entering into a semantic contract with EH system involved that you know how to do exceptional case cleanup correctly. And that is one of the key focus of this point. If the application can decide that this is not the case (i.e. correct cleanup cannot be done) and continuing further may result in undefined behaviour, you may want to consider doing a FailFast.

  • The Boo Programming Language has native support for fault blocks (in addition to filter blocks, like VB.Net).  The fault mechanism can be great to note a failure when you aren't actually able to handle it.

  • @Gaurav - regarding the point about a contract with the EH system: I suggest that when an app crashes, it's time to tear up the contract! :)

    Who knows what the finally blocks will do, what external state they will trash, after an unexpected and unhandled exception has caused termination? Clearly things are not happening according to the plan. A contract somewhere is already not being fulfilled. All bets are off. And even supposing a finally block is able to successfully roll back some external state (deleting a temporary file, or half-completed database record), that might destroy extremely useful evidence for debugging purposes.

  • @Gaurav - re: UnhandleException, I was thinking of Environment.FailFast, yep. I just wonder why that isn't the default for an unhandled exception.

    With the present situation, try/finally blocks are an implicit contract stating the following: "My finally blocks do the right thing, even when I've completely lost control of the state of the program in ways I never anticipated". I'm not sure many people realise those are the terms of the contract, or they'd be reluctant to sign up to it! :) Given that we're talking about bugs here, and they are unfortunately "statistical" in nature (you don't know where the next one will show up), it seems unrealistic to assume that they will never show up in finally blocks, which is what the present default behaviour is predicated on.

    If I had some way to ensure that a finally block's behaviour is always perfectly predictable and causes no additional harm, even in the face of any bug elsewhere in my or 3rd party code, then trust me, I'd be using that same technique on all my code. :) Right now I only have the same techniques as everyone else, and guess what: my success rate is not 100%, and nor is anyone else's.

    (Of course, the downer here is the whole thread-pool thing, where uncaught exceptions get secretly caught so they can switch contexts and be rethrown by EndInvoke. Excitement, adventure and really wild things!)

  • While it could be true with third party libraries, exception handling is not a means to mitigate against bugs in your code.

    The purpose of managed exception handling is to handle ‘exceptional circumstances’ that can be predicted.

    Example:  If when streaming content from a local file to a remote service and network connectivity is lost mid transfer it is not a bug. It's not expected to be normal execution, but it’s predictable that it could occasionally happen. In this scenario you would want to ensure that you dispose of the local FileStream.

    As a true exception is not a bug, it is fair to say that when using finally to handle exceptional circumstances we should be “just as careful” not to write buggy code. I would say we should use only for local cleanup – and we should be able to handle that.

    Also, when re-throwing an exception (see the handling exception guidelines). If a local variable is declared in your catch block, unless you understand the exception as in the case above, don’t re-throw the local variable instance, just use “throw;”. This will result in information about where the exception actually occurred being retained and accessible. Of course this is not an approach to use on public methods, they should catch lower unhandled exceptions, implement instrumentation, and return an exception that does not provide undesired internals to the public domain.

    With the exception of the public methods as already covered, try/catch /finally blocks should have specific exceptional circumstances they are trying to gracefully handle, and this should be the limit of their scope {}.

    In Windows Forms adding a delegate instance to Application.ThreadException results in a try catch on the main form execution so any main Form instance finally blocks will execute. Therefore, it seems sensible to always subscribe to this event. Yes, I know, it’s a no brainer for other reasons, but maybe it should have been in the default VS Win Forms template for the program.cs for this reason? Note: The behaviour is accurately described but the compiler wrapping and resulting IL is not yet confirmed.

    Considering the point made in the article it may be prudent when writing a component library to put additonal thought into how you dispose unmanaged resources in exceptional circumstances. If your type implements IDisposable – as it should when using unmanged resources – then the consumer is reponsible for disposing. However, if they only have a single try catch (possibly via a using block) then your component will not necessarily be correctly disposed! An exception occurring in your code may not result in Dispose being called even if the user has used a using block as this will be a single try/catch! Therefore, you need to implement a try catch where an exception could be anticipated and not rely on the Dispose method being called by the consumer. Then if the consumer has correctly used a Using block your inner finally will be called.

    An aside -

    When debugging, if you have an exception, don’t just stop Visual Studio when on a break point in a catch block. Allow the program to continue through to cleanup or use edit and continue. Otherwise you could find a situation where you have so many unclaimed resources – such as db connections – that you cause locks with the result, that at a minimum, you need to restart some of your machine services.

    Here’s a bug .

    int x = 1;

    int y = 0;

    int answer = x / y;

  • System.Threading.ThreadAbortException is just plain weird. For instance, most exceptions happen because

  • Just a note, the "CRT" behavior to invoke std::terminate on exception escaping a destructor (1) only applies if that destructor is invoked during an unwind and (2) that _particular_ escape would interfere with the current unwind in progress.  

    Sorry to be pedantic.  This is just the C++ programming language, and probably applies even when using managed C++.  It would be interesting to read a more detailed account on how these two features interact.

Page 1 of 1 (14 items)