Contents
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.
This article assumes you are familiar with exception handling in .NET and with C# (or a similar language).
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
{
}
catch (Exception ex)
{
}
: : : : : : : : : :
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.
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:
- 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.)
- 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()
{
: : : : : : : : : :
: : : : : : : : : :
try
{
: : : : : : : : : :
LowerLevelUnreliableMethod();
: : : : : : : : : :
}
catch (ExcpectedException_1 ex) { : : : : : }
catch (ExcpectedException_2 ex) { : : : : : }
: : : : : : : : : :
LowerLevelReliableMethod(); : : : : : : : : : :
: : : : : : : : : :
}
Unexpected exception causing confusion at the upper level:
bool UpperLevelMethod()
{
: : : : : : : : : :
try
{
: : : : : : : : : :
SomeUnreliableMethod();
: : : : : : : : : :
MiddleLevelMethod();
: : : : : : : : : :
SomeUnreliableMethod_1();
: : : : : : : : : :
SomeUnreliableMethod_2();
: : : : : : : : : :
}
catch (SomeException ex)
{
: : : : : : : : : :
return false;
}
: : : : : : : : : :
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.
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.)
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()
{
: : : : : : : : : :
monolithic
{
: : : : : : : : : :
try
{
: : : : : : : : : :
LowerLevelUnreliableMethod();
: : : : : : : : : :
}
catch (ExcpectedException_1 ex) { : : : : : }
catch (ExcpectedException_2 ex) { : : : : : }
: : : : : : : : : :
LowerLevelReliableMethod(); : : : : : : : : : :
}
: : : : : : : : : :
}
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):
- 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):
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.)
- 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 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);
}
: : : : : : : : : :
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;
class ExceptionChainSeperatorException: Exception
{
public ExceptionChainSeperatorException(Exception innerException):
base( "Two different exception chains have been combinated.",
innerException )
{}
}
static class ExceptionChainCombination
{
public static Exception Combine( Exception headException,
Exception innerException )
{
return CombineTwoChains( headException,
new ExceptionChainSeperatorException(innerException) );
}
static Exception CombineTwoChains( Exception headException,
Exception innerException )
{
return NewChainedException( headException,
headException.InnerException==null ? innerException :
CombineTwoChains( headException.InnerException,
innerException ) );
}
static Exception NewChainedException( Exception patternException,
Exception innerException )
{
Exception rslt=null;
try
{
rslt=(Exception)patternException.GetType().InvokeMember
(
null, BindingFlags.CreateInstance, null, null,
new object[]{ patternException.Message, innerException }
);
}
catch (Exception ex)
{
Program.Failure(ex);
}
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;
static class Program
{
static void DoTry()
{
throw new ApplicationException(“Serious Error.”);
}
static void DoFinally()
{
throw new System.IO.FileNotFoundException();
}
static void PerformTryFinally()
{
try {
DoTry();
}
catch (Exception e) {
try {
DoFinally();
}
catch (Exception x) {
throw new DeterministicFinalizationException(
ExceptionChainCombination.Combine(x,e) );
}
throw;
}
DoFinally(); }
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;
: : : : : : : : : :
: : : : : : : : : :
static class Program
{
: : : : : : : : : :
static void PerformDemonstration_Plain()
{
PrintOperation("Starting \"using\"-block");
using (BlockMemoryResource bmr=new BlockMemoryResource())
{
bmr.MaliciousDisposeBug=true;
AccessResourceMemory(bmr);
ThrowSeriousExceptionChain();
}
}
static void PerformDemonstration_Advanced()
{
PrintOperation("Starting \"using\"-block alternative");
BlockMemoryResource bmr=new BlockMemoryResource();
try
{
bmr.MaliciousDisposeBug=true;
AccessResourceMemory(bmr);
ThrowSeriousExceptionChain();
}
catch (Exception e)
{
try
{ bmr.Dispose(); }
catch (Exception x)
{ throw ExceptionChainCombination.Combine(x,e); }
throw;
}
bmr.Dispose();
}
static void PerformDemonstration_AdvancedPlus()
{
PrintOperation("Starting \"using\"-block alternative");
BlockMemoryResource bmr=new BlockMemoryResource();
try
{
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 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
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):