The Back Side of Exceptions

.NET EXCEPTIONS: The Back Side of Exceptions

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

Introduction

Error handling is a general topic. This article is concerned with the strategy based on exceptions (which are a universal mechanism for raising and reporting an error) in relation to .NET. Here, you will learn about two problems of exception handling (that are not covered enough in existing documentation) and see how to prevent some insidious situations, such as misidentification of an exception leaving the program in an inconsistent state, or a 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.

Introducing Exception Handling Problems

Exceptions are used everywhere in .NET. Any language instruction or function call may throw an exception. In comparison with the result code-based strategy, this method gives no chance to a situation where you have missed an error check and the execution continues normally in spite of that error; an exception will be thrown instead. An exception-based API (what .NET is) is more advanced than the result code-based one (at least for user mode coding). Of course, you 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. 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
{
   //< Do 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 naïve to think that such a simple method of exception catching that 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 a relatively rare incident, but it can take place and someday will occur (especially if you use more or less general kinds of catch blocks, like catching 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 to the least specific error. 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) that allows you to certainly avoid program data inconsistency in coding with exception handling. MSDN documentation (at least the one shipped with Visual Studio® 2005) only gives a little notice about side effects in connection with exceptions.

You are approaching the problem. This article explains an insidious side effect of exception catching. It will show the danger and introduce some terms (abstractions) that will allow you to understand what problem situations may exist. These abstractions are helpful to detect rare, unexpected errors that can cause an unrecoverable program sate, and handle them as fatal (so you will always be sure that your application is well). A separate section of the article discusses another back 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 the 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. Suppose there are three code levels: MIDDLE LEVEL (you are currently coding in), LOWER LEVEL (you rely on), and UPPER LEVEL (that calls you). Consider a logical piece of code you have to implement as a transaction. It doesn't mean you must be able to roll back all the actions; you only need to guarantee that your code path cannot be implicitly interrupted and to stay incomplete as the 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 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, you guard all the places where exceptions are expected with try-catch-finally blocks. You normally do not guard those regions where no exception is expected. You simply imply: If such abnormal exception will be thrown (implicitly by your code, or somewhere from the deeper level), let an upper level handle it (or let it be a fatal error). You 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 that 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 depths.)
  2. The upper level code (that called you) catches an exception of a class to which the exception of your lower level code belongs, and handles it as a normal case.

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

Listing 1: Transactional code at the middle level

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

Listing 2: 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 the 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, you should keep in mind that your instruction sequence can be interrupted at any place. Therefore, there is the need in some advanced strategy or even in special language constructions that can protect you from situations such as were described above. (All existing recommendations do not help. Rich exception hierarchy is also ineffective in connection with this problem.) The next section introduces some innovations.

2. Presenting abstract terms: FATAL EXCEPTION and MONOLITHIC CODE BLOCK

Let me introduce two helpful terms that I want that your hypothetical programming system to support to protect you from the side effect of exception catching. These abstractions will help you laterm in your own interpretations based on the existing instruments (you'll see it in the other sections).

Fatal Exception: An exception that is processed in a special way, so when thrown somewhere, it cannot be caught anywhere but the TOP LEVEL. One 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 your 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 a very harmful result).

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 that 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, one fatal exception exists: the System.StackOverflowException exception. It sometimes occurrs (not thrown explicitly by the code); it cannot be caught by a try-caught block. Consequently, the exception causes the process of terminating 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 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 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 cannot 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 ThreadAbortException or ThreadInterruptedException exception with a correspondent try-catch-finally block, this exception is processed in a 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 that your hypothetical compiler should provide to support monolithic code blocks and fatal exceptions

Adopting the above-mentioned abstractions, imagine that your 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 (cannot be interrupted implicitly):
  • monolithic { [...] }
    [...] monolithic Type Function( [...] ) { [...] }
    

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

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.
      : : : : : : : : : :
   }
   : : : : : : : : : :
}

The Back Side of Exceptions

4. How can you really program in terms of a monolithic code block and fatal exception without proper support in the existing infrastructure?

Apart from the monolithic code block, there are some well known constructions/interpretations that C# (and other .NET languages) really supports. These are the following three statements (at the present time): using, foreach, and lock. The compiler interprets each of them as a series of correspondent language instructions. Please review the lock statement, which is a little analogous to the monolithic code block that you want to interpret.

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

A lock statement of 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 monolithic code block looks like the following:

A hypothetical monolithic statement of form

monolithic { : : : : : }

is approximately equivalent to

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

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

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

Another solution does not require total programming style modifications but is suitable only for full-trusted code; that is a so-called Procedural Fatal Error (permissions for entire process termination are required). The task is to perform the fatal error gracefully (not simply to call MessageBox and System.Environment.Exit). To do it, you need to have a helper function somewhere named PerformFatalError, for instance; it is responsible for error reporting and optionally for some additional actions (for example, calling prior 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 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 messages for chained exceptions (InnerException exceptions are implied) and correspondent stack trace listing from the primary exception throwing (in the chain). A well-formed implementation of such a procedural fatal error reports once only, blocking all possible later attempts as dead ends (for example, via System.Threading.Thread.Sleep, specifying millisecondsTimeout of System.Threading.Timeout.Inifinite), while the report is being read by the used and/or is being logged or saved somewhere. (Such an exclusion of the duplicate error reports can be realized via the System.Threading.Interlocked.Exchange operation, for instance).

There is also another fault: You cannot leave a manually implemented monolithic code block via an explicit throw statement (as this abstraction permits); your exception will be considered fatal. So, you are only allowed to use one of the following instructions: break, return, and goto (c'est la vie)

In conclusion, you have two following interpretations (two solutions):

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

    A hypothetical monolithic statement of form

    monolithic { : : : : : }

    is equivalent to

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

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

  2. The monolithic code block implemented with the initiation of a procedural fatal error,suitable for full-trusted code only (requires permission for entire process termination); this method does not depend on anything outside of the block:

    A hypothetical monolithic statement of form

    monolithic { : : : : : }

    is equivalent to

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

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

An exception-based programming strategy assumes that any missing error checking (missing the exception guard here) is a subject of exception handling at an upper level (when that error actually occurs). But, it does not automatically guarantee that such an unexpected exception 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 the monolithic code block and fatal exception, you need to implement proper helper classes according to one of the 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 an interpreted monolithic code block.)

(There is the following radical idea: how to make the earlier mentioned hypothetical programming system maximally reliable. To protect yourself 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 notation. Thus, in each particular case you will have to think whether to mark a function or block as interruptible. This method can give much more reliable results than the use of the monolithic keyword. However, if you were to embed it into an existing .NET language, it is clear that such an interruptible-based implementation would conflict with all currently existing code.)

The Back Side of Exceptions

Another Dark Side: The Loss of the Primary Exception Information as a Result of Another Exception that 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 you (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 that is not discussed in the SDK documentation. To understand further explanation, you should know about exception chain organization via the System.Exception.InnerException property. You also should be familiar with interpretations of the using and foreach C# statements.

Exception in the finally block that 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 a fatal error in .NET (like System.StackOverflowException exception, for instance). Thus, a finally block may be interrupted implicitly, without application failure. By the way, an unhandled exception in the destructor (a non-deterministic finalizer that is called from the garbage collector's thread) causes 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 certainly were designed to perform so-called deterministic finalization at the end (independently of 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 an upper-level catch block, you are not able 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 block exists and an unhandled exception handler is not installed, the default unhandled exception handler will show you both exceptions (text information for the both chains). This is probably the only case for your 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 (
   Console.WriteLine(ex);    // print all the chain of exceptions
                             // for "ex"
}

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

You will try to solve the problem, but you should know about exception chaining to do it. To maintain an 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 an innerException parameter. Sometimes, exceptions also can 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).

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 your bad phenomenon: The InnerException property is read-only and not designed to keep something else except the direct error cause, so you cannot 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 a user-defined fatal error report in your unhandled exception handler). Furthermore, 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, 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 finalization exception chain with the primary one (in chronological order), separating them by special exception: ExceptionChainSeparatorException. This operation is supported by the ExceptionChainCombination.Combine static method (in 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).

Listing 4: Support for combination of two different exception chains separated by ExceptionChainSepatatorException exception

namespace LostExceptionChain
{
   using System;
   using System.Reflection;

   /// <summary>
   /// This exception does not designate an error.
   /// It is specially used to distinguish where a 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 the
      /// <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-read-only properties (the others are lost):
         rslt.HelpLink= patternException.HelpLink;
         rslt.Source  = patternException.Source;

         return rslt;
      }
   }
}

Now, rewrite your simple example you have seen at the beginning of this section by reinterpreting the try-finally block in a special way (not to lose the exception information). See Listing 5.

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 an exception 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 that disposes a 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.)

The Back Side of Exceptions

The program can run in one of three following modes specified by the only possible command line parameter-switch: /Plain, /Advanced, and /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, special transformation of the using statement via two embedded; try-catch blocks, making a combination of two exception chains to prevent the loss of the primary exception; and ADVANCED PLUS, analogous method enforced with insertion of a special DeterministicFinalizationException exception, as a head of the combined chain. See Listings 6 and 7 (all of these modes). Examine C# source files for details.

Listing 6: 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();
      }

      : : : : : : : : : :

   }
}

Listing 7: Three modes of the LostExceptionChain sample program (/Plain, /Advanced, and /Advanced+)

- console output
Performing "LostExceptionChain" demonstration /PLAIN:

* Starting "using"-block...
* Accessing resource's memory...
#  0: 2006
#  1: 7
#  2: 1
#  3: 0
#  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 "LostExceptionChain" demonstration /ADVANCED:

* Starting "using"-block alternative...
* Accessing resource's memory...
#  0: 2006
#  1: 7
#  2: 1
#  3: 0
#  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 "LostExceptionChain" demonstration /ADVANCED+:

* Starting "using"-block alternative...
* Accessing resource's memory...
#  0: 2006
#  1: 7
#  2: 1
#  3: 0
#  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: "Cannot
    completely perform deterministic finalization."

The Balance demo application (which is described in the next section) will show you a higher level of support for advanced deterministic finalization without the loss of main exception information. It provides several constructions related to such finalization (through the helper classes and methods) that 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 about making the earlier mentioned hypothetical programming system maximally reliable. Unhandled exception in the finally block should cause a DeterministicFinalizationException exception, which you should declare as fatal. The finally block is another type of the monolithic code block—by its logic. Therefore, you 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 InnerException property with the help of an ExceptionChainSeparatorException exception. However, if you tried to embed it this way into an existing .NET language, it is clear that there would be some problems with 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 that the figures and the LostExceptionChain sample outline. The Balance program is written in the C# language 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 is logically presented by several blocks). The program has, for example, a part, separated as an ExternalSource namespace (located in one C# file), which is, according to the legend, was 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 that are useful for programming fully 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 so-called compound containers that consist of equal numbers of components. A constraint exists for every such container: The sum of the 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 the invariable per-container sum. (You can think of these values as about money debts, for example, or something else you can imagine.)

Balance uses an algorithm implemented as approximate process based on an operation of inter-component transfer that 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 CompoundContainer.Balance uses a 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 that 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 following items (among others): Fatal Error Test and Lost Exception Chain Demo. The first probes how the program will behave in a situation of fatal error. The second illustrates the phenomenon of loss of exception information similarly to the LostExceptionChain sample application (whose code fragments you have seen in the figures 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, and so forth. (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 that 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 whose links 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 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 that has a problem with its disposition

    Balance.LostExceptionChainDemo (static class): Lost exception chain demonstration (also uses some tricks ito not lose exceptions)

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

  • SysUtils\SysUtils.FatalError.cs: Procedural fatal error support
  • SysUtils\SysUtils.MonolithicSupport.cs: Monolithic code block support
  • SysUtils\SysUtils.ExceptionMessage.cs: Deals 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 the overall unification. The knowledge of reefs under the water will help you in coding for the tasks of the real world.



About the Author

Sergei Kitaev

Sergei Kitaev, born in 1973, lives in Russia (the city of Voronezh). I am a senior programmer at Voronezh Municipal Center of Information and Analysis (municipal communal services). I have been developing and coding for Microsoft Windows: GUI and database applications, some device control programs. Mail-to: serg_kit@mail.ru (Sergei Kitaev).

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Learn How A Global Entertainment Company Saw a 448% ROI Every business today uses software to manage systems, deliver products, and empower employees to do their jobs. But software inevitably breaks, and when it does, businesses lose money -- in the form of dissatisfied customers, missed SLAs or lost productivity. PagerDuty, an operations performance platform, solves this problem by helping operations engineers and developers more effectively manage and resolve incidents across a company's global operations. …

  • Live Event Date: December 18, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT The Internet of Things (IoT) incorporates physical devices into business processes using predictive analytics. While it relies heavily on existing Internet technologies, it differs by including physical devices, specialized protocols, physical analytics, and a unique partner network. To capture the real business value of IoT, the industry must move beyond customized projects to general patterns and platforms. Check out this upcoming webcast …

Most Popular Programming Stories

More for Developers

RSS Feeds