Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

The Back Side of Exceptions

0.00/5 (No votes)
11 Mar 2010 1  
This article illustrates the risks of exceptions being thrown where it is generally not expected, e.g., a library function or a finally block, and shows ways to prevent some insidious errors (such as inconsistency of program data or loss of exception information).

Contents

Introduction

Error handling is a general topic. This article is concerned with strategy based on exceptions (which is a universal mechanism for raising and reporting an error) in relation to .NET. Here, you will learn about two problems of exception handling (which are not covered enough in existing documentation), and will see how to prevent some insidious situations, such as misidentification of an exception leaving the program in an inconsistent state, or loss of exception information. The article has some theoretical flavor, but may also be helpful in practice - the effects described here are quite probable for large programs, especially for multi-module applications developed by different people.

Background

This article assumes you are familiar with exception handling in .NET and with C# (or a similar language).

Introducing exception handling problems

Exceptions are used everywhere in .NET. Any language instruction or function call may throw an exception. In comparison against a strategy which is based on result codes, this method gives no chance to a situation where you have missed an error checking and the execution continues normally in spite of that error - an exception will be thrown instead. Exception-based APIs (what .NET is) are more advanced than the result-code-based ones (at least for user mode coding). Of course, we should be ready to catch an exception in any suspected place (where it may really occur).

.NET offers a rich hierarchy of exception classes. In conjunction with the compiler, it uses a very efficient exception handling mechanism, so execution performance is not affected when a possible exception doesn't occur (only the actual handling is expensive). .NET-oriented languages provide different constructions relative to exception handling. And so forth. But nevertheless, some shortcomings also exist. Look at this abstract but typical code example, like many programmers sometimes write (this snippet and all the others are in C#):

: : : : : : : : : :
try
{
    //< Doing some complex action(s) >
}
catch (Exception ex)
{
    //< Inform a user about the error >
}
//< Continue the execution implying that everything is OK >
: : : : : : : : : :

In the strategy based on result codes, you possibly have to write a lot of checks instead, to process all bad situations correctly. Maybe, you will encounter a very serious error so it will require some special action (not only to unwind the stack and to inform the user). Maybe, you'll decide to stop further execution. It is very naive to think that such a simple method of exception catching which you have seen just now always resolves all the difficulties you could have in the hard result code-based method of error checking. It is nearly like you decide to terminate an auxiliary thread in an arbitrary place by a TerminateThread call (Win32 function): some data in your program may become inconsistent (meaning that further execution is unsafe). Exception catching may cause a similar situation. Such a case is relatively a rare incident, but it can take place some day (especially if you use more or less general kinds of catch blocks, like catching of Exception or SystemException - it's not easy to avoid them everywhere).

As for the example above, of course, you can reorganize it. Apply a compound catch block: catch exception classes (objects) from the most specific error to the least one. Even several hierarchy branches can be used. (Also, the most general catch block can be provided at the end.) You can enforce the try block with inner try-catch-finally sections. But there is no special language construction, and there is no clear recommendation (a universal rule) which allows you to certainly avoid program data inconsistency in coding with exception handling. The MSDN documentation (at least the one shipped with Visual Studio® 2005) only gives a little notice about side effects in connection with exceptions.

We are approaching the problem. This article explains an insidious side effect of exception catching. It will show the danger, and introduce some terms (abstractions) which will allow us to understand what trouble situations may exist. These abstractions are helpful to detect rare unexpected errors that can result in an unrecoverable program state and handling them as fatal (so you will always be sure that your application is well). A separate section of the article discusses another dark side of exceptions - a possible loss of exception information about the original error.

The first problem: implicit interruption of code flow as a result of misidentification of some unexpected exception

1. Situation where the caught abnormal exception is confused with another one considered as a normal case

An insidious side effect may occur under some circumstances. Let us suppose that there are three code levels: middle level (we are currently coding in), lower level (which we rely on), and upper level (which calls us). Let us consider a logical piece of code we have to implement as a transaction. It doesn't mean we must be able to rollback all the actions, we only need to guarantee that our code path cannot be implicitly interrupted and stays incomplete as a result. The term “transaction” is used specifically to describe some break-critical code flow; for example, an obligatory setting of a job completion flag at the end of the started job. Another classic example of such an uninterruptible complex action is a money transfer operation from one monetary account to another. Such an action is performed as two inseparable subsequent steps named Withdraw and Add - both must succeed (otherwise, the states of the accounts are corrupted).

Thus, to perform a so-called transaction, we guard all the places where exceptions are expected, with try-catch-finally blocks. We normally do not guard those regions where no exception is expected. We simply imply: if such an abnormal exception will be thrown (implicitly by our code, or somewhere from the deeper level), let an upper level handle it (or let it be a fatal error). We don't want to handle it at this level - let the default exception handler, for example, catch it.

This strategy usually works well, but two simultaneous conditions will reveal its defect:

  1. The lower level code abnormally throws an exception which is absolutely unexpected, or your code implicitly causes such an exceptional situation. (For the lower level code - suppose it was not documented, because the primary error cause is hidden somewhere in the depth.)
  2. The upper level code (which called us) catches an exception of a class to which the exception of our lower level code belongs, and handles it as a normal case.

What do we have? The abnormal exception from the deeper level is concealed in the upper one, and the execution continues. Some data in the so called transaction became inconsistent (because we may have interrupted it in the middle). The transaction could have access to an instance, static, or in-stack located data, which is now corrupted. But the upper level code thinks that everything is OK (it thinks it has handled some normal error). Thus, the side effect exists. See Listing 1 and Listing 2.

Transactional code at the middle level.

void MiddleLevelMethod()
{
    : : : : : : : : : :
    // === Begin of “transaction” ===
    : : : : : : : : : :
    try
    {
        : : : : : : : : : :
        LowerLevelUnreliableMethod();
        // can cause ExcpectedException_1 or ExcpectedException_2
        : : : : : : : : : :
    }
    catch (ExcpectedException_1 ex) { : : : : : }
    // handling ExcpectedException_1
    catch (ExcpectedException_2 ex) { : : : : : }
    // handling ExcpectedException_2
    : : : : : : : : : :
    LowerLevelReliableMethod(); // <-- UnexpectedException 
                                //     (derived from SomeException)
    : : : : : : : : : :
    // === End of “transaction” ===
    : : : : : : : : : :
}

Unexpected exception causing confusion at the upper level:

bool UpperLevelMethod()
{
    : : : : : : : : : :
    try
    {
        : : : : : : : : : :        
        SomeUnreliableMethod();
        // can cause SomeException
        : : : : : : : : : :
        MiddleLevelMethod();
        // can't cause exceptions
        : : : : : : : : : :
        SomeUnreliableMethod_1();
        // can cause SomeException_1
        // (derived from SomeException)
        : : : : : : : : : :
        SomeUnreliableMethod_2();
        // can cause SomeException_2
        // (derived from SomeException)
        : : : : : : : : : :
    }
    catch (SomeException ex)
    // handling SomeException,SomeException_1,SomeException_2
    {
        // Here we can erroneously catch UnexpectedException,
        // which has broken transaction in the MiddleLevelMethod
        // (but we don't think about such “insignificant details”
        // at this level).
        : : : : : : : : : :
        return false;
        // execution continues (but the program
        // may be in an inconsistent state)
    }
    : : : : : : : : : :
    return true;
}

In a world where exceptions don't exist, almost nothing (except thread termination) can break your code path. In the world of exceptions, we should keep in mind that our instruction sequence can be interrupted at any place. Therefore, there is the need for some advanced strategy, or even special language constructions which can protect us from situations such were described above. (The existing recommendations would not help. A rich exception hierarchy is also ineffective in connection with this problem.) The next subsection introduces some innovations.

2. Presenting abstract terms: Fatal Exception and Monolithic Code Block

Let us introduce two helpful terms, which we wish that our hypothetical programming system will support in order to protect us from the side effect of exception catching. These abstractions will help us later, in our own interpretations based on the existing instruments (you'll see it in the other subsections).

Fatal Exception - an exception that is processed in a special way, so when thrown somewhere, it can not be caught anywhere but the top level. An exception can be fatal by its definition, but another one (which is not declared as fatal) can also be thrown in such a fatal manner.

If our program decides that it is in a catastrophic situation, or something unrecoverable has occurred - it throws a correspondent meaningful exception as fatal, being insured that the application will not function further in such an inconsistent state (that could cause harmful results).

The so called top level means one of the following: for the default application domain - this is a handler routine for an unhandled exception (if installed) / the default unhandled exception handler; for the non-default application domain - this is an outer catch block in the default application domain, or the default domain's unhandled exception handler routine (if installed) / the default unhandled exception handler. For an application which runs in the default application domain (full right GUI or console app., managed service), the System.AppDomain.UnhandledException event serves as the unhandled exception handler. (In the Windows Forms library, the System.Windows.Forms.Application.ThreadException event is used to deal with unhandled exceptions originated from a UI thread, before the top level, but window message pumping can be resumed.)

In the existing infrastructure, a fatal alike exception exists: the System.StackOverflowException exception. If sometimes occurs (not thrown explicitly by the code), it can not be caught by a try-catch block. Consequently, the exception causes the process to terminate immediately. (But our fatal exceptions are a bit more sophisticated.)

Monolithic Code Block - an uninterruptible region within a function, where the code path can not be broken implicitly - only an explicit leaving of this region is admissible: via instructions like throw, break, return, and goto. If an unguarded exceptional situation occurs during monolithic code passing (ether implicitly by this monolithic code - with no use of the throw keyword, or by lower level code that this monolithic code did not guard enclosing with a try-catch-finally block), this unguarded exception causes a MonolithicCodeViolationException exception. This exception is determined as fatal (it can not be caught anywhere but at the top level). The original exception is saved in the InnerException property of the MonolithicCodeViolationException instance as a primary error cause.

(In connection with the monolithic code block, additional rules are required for threading. If some thread is calling System.Threading.Thread.Abort or System.Threading.Thread.Interrupt on a subject thread object while the subject thread is passing through its monolithic code block, the following behavior should take place: if this monolithic code has guarded itself against the ThreadAbortException or ThreadInterruptedException exception with a corresponding try-catch-finally block, this exception is processed the usual way; but if there is no such guard, the exception is deferred until the monolithic code path finishes and the flow reaches the end of the block.)

3. Inventing special keywords which our hypothetical compiler should provide in order to support monolithic code blocks and fatal exceptions

Adopting the above mentioned abstractions, let us imagine that our hypothetical compiler extends the C# language and it understands the following constructions (these are not true compiler syntax diagrams, but they are understandable):

  • Fatal exception declaration (whose instance can only be thrown as fatal): : : : fatalexceptionclass DerivedExceptionClass: ParentExceptionClass.
  • Fatal exception throwing instruction (to throw an exception as fatal): fatalthrow ExceptionObject.
  • Monolithic code block and monolithic function (can not be interrupted implicitly): monolithic { : : : } : : : monolithic Type Function( : : : ) { : : : }.

Having these enhancements, let us rewrite the recent transactional example in a fully protected way. See Listing 3.

Hypothetical monolithic code block (protected from abnormal errors):

void MiddleLevelMethod()
{
    : : : : : : : : : :
    // “Transaction”:
    monolithic
    {
        : : : : : : : : : :
        try
        {
            : : : : : : : : : :
            LowerLevelUnreliableMethod();
              // can cause ExcpectedException_1 or ExcpectedException_2
            : : : : : : : : : :
        }
        // Handling ExcpectedException_1 or ExcpectedException_2:
        catch (ExcpectedException_1 ex) { : : : : : }
        catch (ExcpectedException_2 ex) { : : : : : }
        : : : : : : : : : :
        LowerLevelReliableMethod(); // <-- UnexpectedException
          // (derived from SomeException)
          //  If UnexpectedException will occur -- it will be caught
          // at the top level and nowhere else, so UpperLevelMethod
          // can't accidentally catch it.
        : : : : : : : : : :
    }
    : : : : : : : : : :
}

4. How can we really program in terms of monolithic code blocks and fatal exceptions without proper support in the existing infrastructure?

Apart from monolithic code blocks, there are some well known constructions/interpretations which C# (and other .NET languages) do really support. These are the following three statements (at present): using, foreach, and lock, - the compiler interprets each of them as a series of corresponding language instructions. Let us review the lock statement, which is a little analogous to the monolithic code block that we want to interpret.

The lock statement obtains the mutual-exclusion lock for a given object, executes a statement, and then releases the lock (this interpretation was taken from the C# Language Specification version 1.2.):

A lock statement of the form:

lock(x) : : : : :

where x is an expression of a reference-type, is precisely equivalent to:

System.Threading.Monitor.Enter(x);
try {
        : : : : : : : : : :
}
finally {
    System.Threading.Monitor.Exit(x);
}

except that x is only evaluated once.

A draft interpretation of a monolithic code block looks as follows:

A hypothetical monolithic statement of the form:

monolithic { : : : : : }

is approximately equivalent to:

try {
    : : : : : : : : : :
}
catch (Exception ex) {
    < Handle ex as fatal error >
}

The draft is very simple, but the problem exists: we do not have a way which allows us to re-throw an exception as fatal. (True fatal exception throwing will require corresponding support in the exception handling mechanism.)

In connection with this problem, suppose that we will correct our programming style. If we somewhere catch a System.Exception (the most general catch block), we obligate to filter our MonolithicCodeViolation exception, which is derived directly from System.Exception. (In C#, we use the is keyword and a single throw statement to re-throw an exception object of this type in the very beginning of the catch block.) Of course, now we can throw our MonolithicCodeViolationException exception as fatal, being assured it will not be re-caught somewhere before an unhandled exception handler (or the default exception handler). If our program is a standalone application and we strictly follow such a programming style, this is a solution.

Another solution that does not require total programming style modifications but is suitable only for full-trusted code is the so called Procedural Fatal Error (permissions for entire process termination is required). The task is to perform a fatal error gracefully (not simply to call MessageBox and System.Environment.Exit). To be able to do it, you need to have a helper function named PerformFatalError, for instance, which is responsible for error reporting, and optionally, for some additional actions (for example: calling of already installed emergency callbacks defined by the user). At the end, the function terminates the entire application's process (for example, via System.Environment.Exit, specifying an exitCode of -1) after showing and/or saving the report, so the control will never be returned to its caller. The error information report should include all the messages for chained exceptions (InnerException exceptions are implied) and the correspondent stack trace listing from the primary exception thrown (in the chain). A well formed implementation of such a procedural fatal error reports only once, blocking all possible later attempts as dead-ends (for example, via System.Threading.Thread.Sleep, specifying a millisecondsTimeout of System.Threading.Timeout.Inifinite), while the report is being read by the user, and/or is being logged or saved to somewhere. (Such an exclusion of duplicate error reports can be realized via a System.Threading.Interlocked.Exchange operation, for instance.)

There is also another fault: we can not leave manually implemented monolithic code blocks via explicit throw statements (as this abstraction permits), our exception will be considered as fatal. So, we are only allowed to use one of the following instructions: break, return, or goto (Se-La-Vi).

In conclusion, we have the two following interpretations (two solutions):

  1. Exception forwarding in monolithic code block implementations - requires a special refined programming style throughout your standalone application, where every general catch block (catch for System.Exception) has an enhancement at its beginning, which re-throws the exception object if it is of type MonolithicCodeViolationException (forwarding this fatal error towards the top level):
  2. A hypothetical monolithic statement of the form:

    monolithic { : : : : : }

    is equivalent to:

    try {
        : : : : : : : : : :
    }
    catch (Exception ex) {
        throw 
         new ExceptionSupportNamespace.MonolithicCodeViolationException(ex);
    }

    (A normal leaving of this block via a throw statement is inadmissible here.)

  3. Monolithic code block implemented with initiation of a procedural fatal error - suitable for full-trusted code only (requires permission for entire process termination); this method is not dependent on anything outside of the block:

    A hypothetical monolithic statement of the form:

    monolithic { : : : : : }

    is equivalent to:

    try {
        : : : : : : : : : :
    }
    catch (Exception ex) {
        ErrorSupportNamespace.ExceptionSupportClass.PerformFatalError(ex);
    }

    (Normal leaving of this block via a throw statement is inadmissible here.)

Exception based programming strategy assumes that any missing error checking (missing exception guard here) is a subject of exception handling in an upper level (when that error actually occurs). But it does not automatically guarantee that such unexpected exceptions will be caught at that level properly in every abnormal situation. Using the monolithic statement interpretations introduced here, you can be sure that a failure of the enclosed transaction will be a failure for the application this monolithic code belongs to.

So, when programming in terms of monolithic code blocks and fatal exceptions, you need to implement proper helper classes according to one of two ways (the two interpretations). You should determine which regions in your program are critical for code flow breaks. Enclose such places in try-catch blocks, handling any exception as fatal. Inside these monolithic blocks, guard those places where you expect exceptions, assuming all the remaining space is automatically protected against every abnormal error. (Do not use the throw statement to leave the so interpreted monolithic code block.)

(There is the following radical idea, how to make the earlier mentioned hypothetical programming system maximally reliable. In order to protect ourselves from all abnormal exceptions, all code should be considered as monolithic, by default. Only specially marked functions and blocks should be non-monolithic and therefore safe for implicit interruption. The keyword interruptible is a good candidate for such a notation. Thus, in each particular case, we will have to think whether to mark a function or block as interruptible or not. This method can give much more reliable results than the use of the monolithic keyword. However, if we were to embed it into an existing .NET language, it is clear that such interruptible-based implementation would conflict with all currently existing code.)

Another dark side: the loss of primary exception information as a result of another exception, which is thrown from the finalization phase of the first one

Another dark side of exceptions is the loss of exception information about the primary error because of another exception thrown from the finally block. How can we (with special tricks) save all possible exceptions linked in a chain via the InnerException property (from the primary to the last)?

This section raises another problem, which is not discussed in the SDK documentation. To understand the following explanation, you should know about exception chain organization via the System.Exception.InnerException property. You should also be familiar with interpretations of the using and foreach C# statements.

Exception in a finally block which originates from a deeper code level or implicitly from the block (not by an explicit throw statement) is an abnormal situation, but this is not considered as a fatal error in .NET (like the System.StackOverflowException exception, for instance). Thus, a finally block may be interrupted implicitly, without application failure. By the way, unhandled exceptions in the destructor (non-deterministic finalizer, which is called from the garbage collector's thread) causes the execution of the unhandled exception handler in .NET 2.0, - thus, such an exception is handled as fatal (at the top level).

A rarely occurring bad phenomenon exists with respect to the finally block and derivative constructions: using and foreach statements (implying the finalization aspect of foreach). All these blocks were designed to perform the so called deterministic finalization at the end (independently from how you leave the block). When such deterministic finalization is caused by an exception in the correspondent (actual or implied) try block and another exception is thrown during this finalization phase, the primary exception information (exception information chain) can be lost. It takes place in any case when an upper level exception guard works. In such upper level catch blocks, you are unable to know about the primary exception (primary exception chain), having only those exception(s) that have occurred in the later phase of finalization.

If no upper level catch blocks exist and an unhandled exception handler is not installed, the default unhandled exception handler will show you both the exceptions (text information for both the chains). But this is probably the only case for our situation when the original error information is not lost.

Look at the following example:

: : : : : : : : : :
try {
    try { throw new ApplicationException(“Serious Error.”); }
    finally { throw new System.IO.FileNotFoundException(); }
}
catch (Exception ex) {
    Console.WriteLine(ex);
    // print all the chain of exceptions for “ex”
}
: : : : : : : : : :

In the catch block above, you do not have the Serious Error information available (System.ApplicationException), it is lost in this place (test this example to be sure). Thus, the bad phenomenon does exist.

Further, we will try to solve the problem, but we should know about the exception chaining in order to do it. To maintain the exception sequence as a chain, the System.Exception class has a special public instance property named InnerException. It serves to store an error that caused the current exception. This property is read-only; it returns the same value as was passed into the constructor (or null). Often, exceptions are chained explicitly - by catching an exception and throwing another one, specifying the original instance in the constructor as the innerException parameter. But sometimes, exceptions can also be chained implicitly. Look at this simple static class below (it has a problem in its static constructor):

: : : : : : : : : :
static class TroubleClass
{
    static TroubleClass()
    {
        throw new ApplicationException(“Original Error.”);
    }
    public static void Touch() {}
}
: : : : : : : : : :

Calling the TroubleClass.Touch static method causes a System.TypeInitializationException exception whose InnerException property contains a link to the System.ApplicationException object (the original error cause).

Let us now return to the problem of exception information loss. At first sight, there is no correct way to save two different exception chains with the help of InnerException in connection with our bad phenomenon: the InnerException property is read-only, and not designed to keep something else except the direct error cause, so we can not simply connect the chains together. (Of course, the System.Exception.Data instance property could be used for this purpose, but such a realization would not look good.)

Error analysis based on exception chain scan is not an easy task. You should not implement it often, analyzing only the last exception in the chain (the currently handled exception) as a rule. But there are some critical situations where saving all of the errors is essential (for example, for user defined fatal error reports in your unhandled exception handler). What goes further - these are only tricks, but they will show that the problem can be solved (at least theoretically).

All the tricks and ideas are implemented in the LostExceptionChain sample program, which is attached to the article. This is a simple console application; it is written in C# to run under .NET 2.0. You will see some of its code fragments while reading the remaining part of this section.

The main idea of saving all the errors is to combine the finalization exception chain with the primary one (in chronological order), separating them by a special exception: the ExceptionChainSeparatorException. This operation is supported by the ExceptionChainCombination.Combine static method (in the LostExceptionChain sample). Because the InnerException property is read-only, a reproduction of the head part (head chain exceptions) is performed. (Unfortunately, read-only exception properties can't be copied, so some information of the head part exceptions is lost.) See Listing 4 (but don't go deep into details).

Support for combination of two different exception chains separated by the ExceptionChainSepatatorException exception.

namespace LostExceptionChain
{
    using System;
    using System.Reflection;
 
    /// <summary>
    /// This exception does not designates an error.
    /// It is specially used to distinguish
    /// where new exception chain begins.
    /// </summary>
    class ExceptionChainSeperatorException: Exception
    {
        public ExceptionChainSeperatorException(Exception innerException):
            base( "Two different exception chains have been combinated.",
                  innerException )
        {}
    }
 
    /// <summary>
    /// This class is used for exception chains combination.
    /// </summary>
    static class ExceptionChainCombination
    {
        /// <summary>
        /// This function combines two exception chains
        /// and separates them by
        /// <see cref="ExceptionChainSeperatorException" />.
        /// Exception object reproduction is used.
        /// It is needed because we can not simply correct
        /// <see cref="System.Exception.InnerException" /> property,
        /// which is read-only.
        /// </summary>
        public static Exception Combine( Exception headException,
                                         Exception innerException )
        {
           return CombineTwoChains( headException,
              new ExceptionChainSeperatorException(innerException) );
        }
 
        /// <remarks>
        /// Recursion is used in this implementation.
        /// </remarks>
        static Exception CombineTwoChains( Exception headException,
                                           Exception innerException )
        {
            return NewChainedException( headException,
                headException.InnerException==null ? innerException :
                CombineTwoChains( headException.InnerException,
                                  innerException ) );
        }
 
        /// <summary>
        /// This function reproduces exception by its pattern
        /// making it chained with <paramfer name="innerException" />
        /// exception.
        /// </summary>
        /// <remarks>
        /// Read-only properties can't be reproduced,
        /// so some informaion is lost.
        /// </remarks>
        static Exception NewChainedException( Exception patternException,
                                              Exception innerException )
        {
            Exception rslt=null;
 
            try
            {
                rslt=(Exception)patternException.GetType().InvokeMember
                (
                    null, BindingFlags.CreateInstance, null, null,
                    new object[]{ // arguments for exception constructor:
                        patternException.Message, innerException }
                );
            }
            catch (Exception ex)
            {
                Program.Failure(ex);
            }
 
            // Saving non-readonly properties (but the others are lost):
            rslt.HelpLink= patternException.HelpLink;
            rslt.Source  = patternException.Source;
 
            return rslt;
        }
    }
}

Let us now rewrite our simple example we saw at the beginning of this section, by reinterpreting the try-finally block in a special way (in order not to lose the exception information). See Listing 5.

Special transformation of a try-finally block (not to lose exception information).

using System;
using ExceptionSupportNamespace;
// special exception handling support
 
static class Program
{
    static void DoTry()
    {
        throw new ApplicationException(“Serious Error.”);
    }
 
    static void DoFinally()
    {
        throw new System.IO.FileNotFoundException();
    }
 
    static void PerformTryFinally()
    {
        // Special interpretation for try-finally block:
        try {
            DoTry();
        }
        // It is used instead of implied finally block:
        catch (Exception e) {
            // Doing finalization caused by exception in the try block:
            try {
                DoFinally();
            }
            catch (Exception x) {
                // Using ExceptionSupportNamespace namespace --
                // throwing combined exception chain
                // separated by ExceptionChainSeparatorException
                // and prefixed with DeterministicFinalizationException:
                throw new DeterministicFinalizationException(
                    ExceptionChainCombination.Combine(x,e) );
            }
            throw;
            // re-throwing the exception
            // (its stack trace remains unchanged)
        }
        DoFinally(); // doing normal finalization
    }
 
    static int Main()
    {
        try
            { PerformTryFinally(); }
        catch (Exception ex)
            { Console.WriteLine(ex); return 1; }
        return 0;
    }
}

The sample program, LostExceptionChain, is designed to illustrate how exceptions can be lost (when you use deterministic finalization) and what tricks may exist to prevent such loss. The demonstration is based on the using block, which disposes the specially designed BlockMemoryResource resource object at the end. The Dispose method of this resource can cause an error - two chained Serious Error exceptions of type System.ApplicationException. (You should be familiar with the System.IDisposable interface here.)

The program can run in one of the three following modes specified by the only possible command line parameter-switch: /Plain, /Advanced, or /Advanced+. (It also starts with empty parameters, simply showing the syntax.) These modes correspond to the following interpretations of the using block:

  • Classic - the natural using statement;
  • Advanced - a special transformation of the using statement via two embedded try-catch blocks - making a combination of two exception chains in order to prevent the loss of the primary exception; and
  • Advanced Plus - an analogous method enforced with the insertion of a special DeterministicFinalizationException exception - as a head of the combined chain.

See Listing 6 and Listing 7 (all of these modes). Examine the C# source files for details.

Three modes of the LostExceptionChain sample program (/Plain, /Advanced, and /Advanced+).

namespace LostExceptionChain
{
    using System;
    : : : : : : : : : :
 
    : : : : : : : : : :
 
    /// <summary>
    /// Class, where <see cref="Main"/> entry-point routine is located.
    /// </summary>
    static class Program
    {
 
        : : : : : : : : : :
 
        /// <summary>Classic using-block.</summary>
        static void PerformDemonstration_Plain()
        {
            PrintOperation("Starting \"using\"-block");
 
            using (BlockMemoryResource bmr=new BlockMemoryResource())
            {
                // Resource disposition will cause an exception(s):
                bmr.MaliciousDisposeBug=true;
 
                AccessResourceMemory(bmr);
 
                ThrowSeriousExceptionChain();
            }
        }
 
        /// <summary>Using-block alternative.</summary>
        static void PerformDemonstration_Advanced()
        {
            PrintOperation("Starting \"using\"-block alternative");
 
            BlockMemoryResource bmr=new BlockMemoryResource();
            try
            {
                // Resource disposition will cause an exception(s):
                bmr.MaliciousDisposeBug=true;
 
                AccessResourceMemory(bmr);
 
                ThrowSeriousExceptionChain();
            }
            catch (Exception e)
            {
                try
                    { bmr.Dispose(); }
                catch (Exception x)
                    { throw ExceptionChainCombination.Combine(x,e); }
                throw;
            }
            bmr.Dispose();
        }
 
        /// <summary>Using-block alternative.</summary>
        /// <remarks>
        /// <see cref="DeterministicFinalizationException"/> is inserted
        /// as a head of combined exception chain.
        /// </remarks>
        static void PerformDemonstration_AdvancedPlus()
        {
            PrintOperation("Starting \"using\"-block alternative");
 
            BlockMemoryResource bmr=new BlockMemoryResource();
            try
            {
                // Resource disposition will cause an exception(s):
                bmr.MaliciousDisposeBug=true;
 
                AccessResourceMemory(bmr);
 
                ThrowSeriousExceptionChain();
            }
            catch (Exception e)
            {
                try
                {
                    bmr.Dispose();
                }
                catch (Exception x)
                {
                    throw new DeterministicFinalizationException(
                        ExceptionChainCombination.Combine(x,e) );
                }
                throw;
            }
            bmr.Dispose();
        }
 
        : : : : : : : : : :
 
    }
}

Three modes of the LostExceptionChain sample program (/Plain, /Advanced, and /Advanced+) - console output.

Performing the "LostExceptionChain"-demonstration /PLAIN:
 
* Starting "using"-block...
* Accessing resource's memory...
#  0: 2006
#  1: 7
#  2: 1
#  3: 1
#  4: 0
#  5: 0
#  6: 0
#  7: 0
#  8: 0
#  9: 0
# 10: 0
# 11: 0
# 12: 0
# 13: 0
# 14: 0
# 15: 0
# 16: 0
# 17: 0
# 18: 0
# 19: 0
* Throwing exception chain of serious application errors...
 
## ERROR IN THE APPLICATION HAS OCCURRED!
=== Exception chain (from primary to the last one): ===
--> System.IO.FileNotFoundException: "Unable to find the specified file."
--> System.Runtime.InteropServices.SEHException: 
    "Exception of type 'System.Runtime.InteropServices.SEHException' was thrown."
 
Performing the "LostExceptionChain"-demonstration /ADVANCED:
 
* Starting "using"-block alternative...
* Accessing resource's memory...
#  0: 2006
#  1: 7
#  2: 1
#  3: 1
#  4: 0
#  5: 0
#  6: 0
#  7: 0
#  8: 0
#  9: 0
# 10: 0
# 11: 0
# 12: 0
# 13: 0
# 14: 0
# 15: 0
# 16: 0
# 17: 0
# 18: 0
# 19: 0
* Throwing exception chain of serious application errors...
 
## ERROR IN THE APPLICATION HAS OCCURRED!
=== Exception chain (from primary to the last one): ===
--> System.ApplicationException: "Serious Error (original)."
--> System.ApplicationException: "Serious Error (subsequent)."
--> LostExceptionChain.ExceptionChainSeperatorException: 
    "Two different exception chains have been combinated."
--> System.IO.FileNotFoundException: "Unable to find the specified file."
--> System.Runtime.InteropServices.SEHException: 
    "Exception of type 'System.Runtime.InteropServices.SEHException' was thrown."
 
 
 
Performing the "LostExceptionChain"-demonstration /ADVANCED+:
 
* Starting "using"-block alternative...
* Accessing resource's memory...
#  0: 2006
#  1: 7
#  2: 1
#  3: 1
#  4: 0
#  5: 0
#  6: 0
#  7: 0
#  8: 0
#  9: 0
# 10: 0
# 11: 0
# 12: 0
# 13: 0
# 14: 0
# 15: 0
# 16: 0
# 17: 0
# 18: 0
# 19: 0
* Throwing exception chain of serious application errors...
 
## ERROR IN THE APPLICATION HAS OCCURRED!
=== Exception chain (from primary to the last one): ===
--> System.ApplicationException: "Serious Error (original)."
--> System.ApplicationException: "Serious Error (subsequent)."
--> LostExceptionChain.ExceptionChainSeperatorException: 
    "Two different exception chains have been combinated."
--> System.IO.FileNotFoundException: "Unable to find the specified file."
--> System.Runtime.InteropServices.SEHException: "Exception of type 
    'System.Runtime.InteropServices.SEHException' was thrown."
--> LostExceptionChain.DeterministicFinalizationException: 
    "Can not completely perform deterministic finalization."

The Balance demo application (which is described in the next section) will show you a higher level support for advanced deterministic finalization without the loss of the main exception information. It provides several constructions related to such finalization (through the helper classes and methods), which can co-operate with your anonymous methods (introduced in C# 2.0) that have the following corresponding roles: Try-role, Finally-role, and Using-role.

(There is the following radical idea, how to make the earlier mentioned hypothetical programming system maximally reliable. Unhandled exception in a finally block should cause a DeterministicFinalizationException exception, which we should declare as fatal. The finally block is another type of monolithic code block - by its logic. Therefore, we should not allow such a situation when this block is interrupted implicitly. Also, as described above, the primary exception chain should be combined with the posterior chain via the InnerException property, with the help of the ExceptionChainSeparatorException exception. However, if we tried to embed it this way into an existing .NET language, it is clear that there would be some problems with the currently existing code.)

A sample program: the Balance demo application

A sample application, Balance, is attached to this article. It realizes (and visualizes) the exception handling ideas presented above - for everybody who wants to see them in action. It completes the picture which the listings and the LostExceptionChain sample outline. The Balance program is written in C# to run under .NET 2.0. This is a GUI application - it is based on the Windows Forms library.

Abnormal situations, which are the focus of this article, occur mainly in large programs (especially in multi-module applications developed by different people). Therefore, Balance was specially designed in a multi-block programming way (it is compiled into one binary module, but logically presented by several blocks). The program has, for example, a part, separated as the ExternalSource namespace (located in a C# file), which is, according to the legend, written by a third-party developer and contains some error in the low level code (in terms of the monolithic code block). Another separate namespace is SysUtils (located in a set of C# files) - a static library of special utility classes which are useful for programming full-fledged Windows Forms applications. The main application files belong to the Balance namespace. (Subfolders are used for source files grouping.)

Apart from the exception handling illustration aspect, the general purpose of Balance is very abstract. There are the so called compound containers consisting of equal numbers of components. A constraint exists for every such container: the sum of components is a constant value. There is also a list of per-component target summaries - by every component through all the containers. The aim is to achieve the targets, or to approach them. It is performed by changing (balancing) component values so that such manipulations don't violate the constraint of invariable per-container sum. (You can think of these values as money debts, for example, or something else you can imagine.)

Balance uses an algorithm implemented as an approximate process based on an operation of inter-component transfer which is internal for the container (it is named “balance operation”). Under such operations, per-container sums remain equal to their initial values, but the total per-component summary values approach the target summaries.

You can visually watch how the approximation evolves. The Balance transfer operation routine named as CompoundContainer.Balance uses the System.ArgumentException exception to report to its caller that the transfer is impossible (in the case, when the operation would result in a negative value). Such a method is used instead of simply returning a System.Boolean value, only for demo purposes - demonstration of monolithic/non-monolithic blocks is based on this way. The main application's form has two checkboxes which allow you to switch between the modes of its behavior - in connection with any abnormal exceptions during the balance operation.

The main menu of Balance has two items (among others): Fatal Error Test and Lost Exception Chain Demo. The first is used to probe how the program will behave in a situation of fatal error. The second one illustrates the phenomenon of loss of exception information, similar to the LostExceptionChain sample application (whose code fragments you have seen in the listings for this article).

This program also contains many interesting features: two-threaded UI, simple 2D-drawing, sound effects, XML data loading/saving, user scoped application settings, debug tracing, etc. (The source code is decorated in a special way. Maybe you'll find that the general style of indention is violated, but such decoration is quite convenient for large files of code which are used in the application. Special notation is employed to designate different kinds of data members.)

The program is large enough, but you need to examine only separate pieces to see the implementation of the key ideas. Navigate through the code fragments which are listed below.

The main Balance key classes and functions (look in the C# source files):

  • MainCode\CompoundContainer.cs:
    • Balance.CompoundContainer.Balance (instance method) - transactional action at the middle level code (monolithic/non-monolithic balance operation).
  • MainCode\Approximation.cs:
    • Balance.Approximation.PerformIteration (instance method) - upper level code (in terms of the monolithic code block), it uses Balance.CompoundContainer.Balance.
  • ExternalSource\ExternalSource.BarBox.cs:
    • ExternalSource.BarBox.RedrawBar (instance method) - lower level code (in terms of the monolithic code block), it is used by Balance.CompoundContainer.Balance.
  • MainCode\Program.cs:
    • Balance.Program.ThreadExceptionEventHandler (static method) - Windows Forms UI thread unhandled exception handler.
  • MainCode\XMLData.cs:
    • Balance.XMLData.LoadApproximation (instance method) - fanatical error checking (implementing an unexpected exception filter in C#).
  • MainCode\FormMain.Satellites.cs:
    • Balance.TroubleClass (static class) - demonstration of exception chain throwing.
    • Balance.TroubleResource (disposable object class) - fictitious resource, which has a problem in its disposition.
    • Balance.LostExceptionChainDemo (static class) - lost exception chain demonstration (also uses some tricks in order to not lose exceptions).

Additional Balance links - some units from the special static library SysUtils (see the C# source files):

  • SysUtils\SysUtils.FatalError.cs - procedural fatal error support
  • SysUtils\SysUtils.MonolithicSupport.cs - monolithic code block support
  • SysUtils\SysUtils.ExceptionMessage.cs - dealing with exception messages
  • SysUtils\SysUtils.ExceptionCombination.cs - combination of two different exception chains
  • SysUtils\SysUtils.ExceptionGuard.cs - exception guard relative to deterministic finalization
  • SysUtils\SysUtils.AutoDispose.cs - special support for using and foreach blocks

Conclusion

The purpose of this article is to point out certain exception handling issues and to discuss some possible remedies. It does not mean that the proposed methods and tricks should be used widely, but these ideas have the right to live. The first idea is that throwing an exception out of a critical code section should be made impossible. In practice, this may require that any unexpected exception be intercepted at the critical section boundary and treated as a fatal error. The second idea is: no error information should be lost in the process of exception handling.

Not every aspect of modern programming has an ideal representation in our days of overall unification. The knowledge of reefs under the water will help you in coding for the tasks of the real world.

Related articles (by Sergei Kitaev):

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here