Exception – How to raise and retrieve error information whithout losing it?


Small icon All .NET applications at sometime will have to deal with an exception. It is just a matter of time. As application run longer in PRODUCTION phase than in development (unless it is a prototype ;)) the more information you can get out of the exception, the more likely you will be able to quickly pinpoint the root cause and resolve the issue.

I know, this might seem a little too basic, but there are still some misuses of Exception and misunderstanding about how to raise/re-raise exception without losing the original information. So here it is my contribution to help to improve the “good” use of Exception programming in .NET (samples are in C#, but the principles are valid for the .NET platform).

(Source code available here ExceptionsDemo.zip – rename the file with a “.zip” extension)

1 – The basics

You can think of an Exception as a piece of data (with hopefully useful information) that once raised will disrupted the normal flow of function or application. By catching it, your code can then decide on whether or not to return to a normal flow, let it raise again or even add further information.

Exception can be raised in one place to indicate an unexpected failure or an abnormal use of a function/method. Then another piece of code would catch the exception and act upon it for various reasons (logging, audit, security, retry ….). The decoupling between the location that ‘raise’ and the one that do the ‘catch’ is for me the main added value of Exception programming.

In .NET, if an exception raises up to the CLR without finding a catch block that handles the exception, the default exception handler handles it and the process will be terminated.

The good things about Exception programming are:
– The source (i.e. the location) raising the exception is decoupled from where it will be caught and act upon. It might be the direct callee method, or it could be the previous 100th call method on the stack.
– Along the raising path, there might be more than one location to caught the exception and act on it.
– The exception can be enriched along its raising path.

Andrew Pardoe [Ref -03] wrote a good article about the evolution from return codes to Exception programming.

I must also mention that Exception programming add a “little overhead” and could have in some circumstances a negative impact on performance (e.g. looping 1,000,000 with many try/catch embedded). It is up to you to test your code – normal path and abnormal path – to find out how your code react. However, as I said earlier, your code will run most of its time in the PRODUCTION environment, and to me having usuful information when facing a PRODUCTION issue has a bigger value that counter balance the “little overhead” issue.

In this blog I will present the different ways to raise, re-raise and catch exceptions.

Chain of calls

1.1 – How to raise an exception?

In .NET C#, you will raise an exception by using the keyword ‘throw’, like this:

throw new InvalidOperationException("Zero cannot be a valid result for this implementation.");

Or like this:

   InvalidOperationException ex = new InvalidOperationException("Zero cannot be a valid result for this implementation.");
ex.Data.Add("m", m);
ex.Data.Add("n", n);
ex.Data.Add("result", result);
throw ex;

Raising the exception is the first step in providing useful information: a clear error message and the call stack.

The important thing to note is that the number of the line in the call stack will be the one where the exception was raised, i.e. where the call to ‘throw’ is made (line #5) – not the number of line where the exception was instantiated (i.e. ‘new…’ in line #1) – Line numbers in the call stack are only available if the “.pdb” (Program database files – you have to switch the compilation switch to produce those files in ‘Release’ flavour – they are automatically generated in ‘Debug’ flavour) files are deployed and available when the exception is raised.

In the last example above, the code is showing the use of the property ‘Data’ to add further information. This is a good thing that application can use to transmit further information about the exception (paramerters, local value).  ‘Data’ is a dictionary the old fashion way: Dictionary[object; object] (that was before the generics existed on .NET).  So you have to be becareful about:

  • The type use for ‘key’. I usually use ‘string’, but as an object it could be anything.
  • The ‘key’ must be unique, so if you use ‘string’ there might be some naming collision. Usually, I use a name based on pattern like this: <MyApp>.<MyClass>.<MyMethod>.<MyKeyName>.
  • ‘Value’ can be anything.

As you will see further down, the exception can go through many exception handling blocks before being used, so you have to think about the data you want to store in ‘Data’ and decide whether or not in your case storing sensitive data is fine or not. Also, if you put any reference to object(s) they will be directly accessible (via ‘Data’) by any code above your method, so maybe you should consider cloning your objects or only pass value types.

1.2 – How to catch an exception?

Using the try/catch block, you can catch an exception:

try
{
// some code that will raise an exception
}
catch (Exception ex) // Exception handling block
{
// You have access to the exception raised here;
}

You can define more than one exception handling block. In that case, you must attention to the existing inheritance between the various exception classes you will be using for your handling blocks: define the more specialized exception class first, then the more generic ones.

E.g. from an inheritance point of view we have the following inheritance relationship for ‘ArgumentNullException’:

System.Object
  System.Exception
    System.SystemException
      System.ArgumentException
        System.ArgumentNullException

In that case you should defined you handling block like that:

            ...
            catch (ArgumentNullException nullEx)
            { ... }
            catch (SystemException sysEx)
            { ... }
            catch (Exception ex)
            { ... }

1.3 – What is available?

The base class System.Exception [Ref-01] has defined some useful fields:

Property Get/Set Description
Data Get Gets a collection of key/value pairs that provide additional user-defined information about the exception.
InnerException Get Gets the Exception instance that caused the current exception.
Message Get Gets a message that describes the current exception.
Source Get/Set Gets or sets the name of the application or the object that causes the error.
StatckTrace Get Gets a string representation of the frames on the call stack at the time the current exception was thrown. This is even more useful if you have the pdb files deployed when the exception is raised.
TargetSite Get Gets the method that throws the current exception.

Please note that some properties are not available on all the .NET Frameworks –  E.g. ‘Data’ and ‘Source’ are not available on the .NET Compact Framework. Please refer to the corresponding MSDN documentation for your version of .NET Framework.

2 – What to do with the exception caught?

At this stage, you caught an exception, you have mainly 3 choices:

  1. Do nothing.
  2. Re-raise the exception
    1. The original one + stack trace. Optionally, can add further info via ‘Data’ property.
    2. Reset the stack trace.
  3. Raise a new exception
    1. Brand new one – lost info about the original
    2. Use of InnerException

2.1 – Do nothing (NOT RECOMMENDED, BUT SOMETIMES CANNOT DO OTHERWISE)

At least you should log or trace the exception.
In some occasions, you may have to refer to this case, i.e. ignore or swallow the exception raised. E.g., using a third-party assembly, the first call to their initialisation code was failing the first time and raising an exception. The second time it was working. While waiting for a fix from the editor, I implemented a catch() block that ignore the exception the first time (but logged it).

On other occasions, I have to refer to this case when writting another ‘try/catch’ block within a ‘finally’ block (see below for further details).

2.2 How to re-raise an exception?

During a chain of method calls, a piece of code might catch an exception to do some process on it (logging, audit, …), but does not want to stop the exception handling here. In that case, it must re-raise (or re-throw) the exception.
There 2 possibilities:
1) Re-raise the original Exception (with the original stack trace)
2) Re-raise the original Exception and reset the call stack

2.2.1 Re-raise as it was

In this case we want to re-raise the exception as it was (line #8):

try
{
// some code that will raise an exception
}
catch (Exception ex)
{
// You have access to the exception raised here;
throw;
}

In this case, just use the word, “throw;”. The original exception and its call stack will be preserved.

2.2.2 Re-raise it by resetting the call stack – NOT RECOMMENDED

In this case, you can re-throw the same exception like this (line #8):

try
{
                // some code that will raise an exception
}
catch (Exception ex)
{
// You have access to the exception raised here;
throw ex1; // Reset the call stack to this frame (i.e. this line of code becomes the bottom of the call stack !!!)
}

I do not recommend this usage because the call stack is reset, so you will lose a lot of useful information during the support phase of your application:
=> You loose the original line of code that thrown the exception, and all the call chain.
=> The new line fo code in the call stack is the line #8 of “throw ex1;” – not so useful!

Also, I could not think of a valid scenario for this case – I do not even understand why the CLR allows this case – Please, I am curious, shared with me if you have a valid scenario for this one. As for me, I keep chasing down this usage on every project I worked on, because when it happens in PRODUCTION we will not have the original call stack.

2.3 Raise a new exception

Again here there are 2 possibilities:
1) Raise a brand new exception and forget about the original one.
2) Raise a new exception and set the InnerException field with the old one.

2.3.1 Raise a brand new exception

In this case we want to raise a fresh new exception and forget about the original one (line #8):

try
{
// some code that will raise an exception
}
catch (Exception ex)
{
// You have access to the exception raised here;
                throw new ArgumentException("Wrong Argument");
}

This is sometimes necessary, e.g. when you want to prevent some sensitive data (server name, IP address, user info, …) to bubble up to the surface. Creating a brand new one with a general but useful message is a solution in that case.
However, I will advise to still log the original one, e.g. in an encrypted log file if it is required, but log it.

2.3.2 Raise a new exception and set the innerException field

In this case, you want to add some information along side the original exception like this (setting the parameter name to “m” – line #8):

try
{
// some code that will raise an exception
}
catch (Exception ex)
{
// You have access to the exception raised here;
                throw new ArgumentException("Wrong Argument", "m", ex);
}

This is fine, as the original exception is preserved with its information (mainly the call stack) and further information was added.

3 – The default AppDomain exception handler

The code you write in .NET will execute within an ApplicationDomain (System.AppDomain). This is the secure boundary context on the .NET platform to run code.  A process could have more than one AppDomain. The AppDomain has an event called ‘UnhandledException’ [Ref-02] to which your code can regsiter a handler like this:

static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
...
}
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Console.Out.WriteLine("CurrentDomain_UnhandledException");
if (e != null)
{
Console.Out.WriteLine("IsTerminating: '{0}'", e.IsTerminating);
if (e.ExceptionObject != null)
Console.Out.WriteLine("ExceptionObject: '{0}'", e.ExceptionObject.ToString());
}
}

The .NET platform had various behaviours regarding how dealing with this event. Please refer to the MSDN documentation for further details, but here are the main points:

  • This event (UnhandledException) provides notification of uncaught exceptions.
  • The system default handler reports the exception to the user (debuging options).
    • .NET 1.1 : debugging options are reported to the user before this event is raised.
    • Since .NET 2.0: event is raised then debugging options are reported to the user.
  • The system default handler terminatesthe application.
    • .NET 1.1 : An unhandled exception that occurs in a thread other than the main application thread is caught by the runtime and therefore does not cause the application to terminate. Thus, it is possible for the UnhandledException event to be raised without the application terminating.
    • Since .NET 2.0: This backstop for unhandled exceptions in child threads was removed, because the cumulative effect of such silent failures included performance degradation, corrupted data, and lockups, all of which were difficult to debug.
  • This event can be handled in an application domain.
    • .NET 1.1 : this event occurs only for the default application domain that is created by the CLR when an application is started.
    • Since .NET 2.0 (extract from MSDN [Ref-02] ):

If the UnhandledException event is handled in the default application domain, it is raised there for any unhandled exception in any thread, no matter what application domain the thread started in. If the thread started in an application domain that has an event handler for UnhandledException, the event is raised in that application domain. If that application domain is not the default application domain, and there is also an event handler in the default application domain, the event is raised in both application domains.

For example, suppose a thread starts in application domain “AD1”, calls a method in application domain “AD2”, and from there calls a method in application domain “AD3”, where it throws an exception. The first application domain in which the UnhandledException event can be raised is “AD1”. If that application domain is not the default application domain, the event can also be raised in the default application domain.

Please, refer to the MSDN documentation, [Ref-02] & [Ref-04], as there are other special cases that you might need to look at more in details depending in your code and usage of threads. E.g. what happens for the ‘finalizer thread’ or ‘worker thread’ from the .NET thread Pool (I am not putting this information here, otherwise this entry will start deriving on ‘multithread programming’ and I want it to stay on ‘Exception programming on the .NET platform’).

As I mentioned early, there some exceptions for which the CLR will not execute your catch{} or finally{} blocks, e.g. when a “StackOverflow” or “Access violation” exception is raised. In those cases, the CLR will assume that your process is in a very bad state and will terminate your process. With the .NET v4.0 the CLR put in place a flag (either in the config file or by using an attribute in code called ‘[HandleProcessCorruptedStateExceptions]‘) that you can use to indicate you want your handler to be called.

There is also an excellent article about “Handling Corrupted States Exceptions” (aka, StackOverflow and Access violation) written by Andrew Pardoe [Ref #03].

4 – The optional ‘finally’ block

You can add a ‘finally’ block at the end of your try/catch block(s).

finally‘ is an interesting construct as it allows your code inside it to be executed whatever happened in the try/catch block(s) (except for a few cases, e.g. if a “StackOverflow” exception is raised).

However, you should be careful about what you are doing inside the ‘finally’ block:
1) Check for ‘null’ references before using a object
2) You might need to have another try/catch block to catch any new exception, so you do not loose the original exception.

Conclusion

I hope that by now you have a better understanding of try/catch/finally blocks and better knoweldge on the various away to raise/re-raise, add information or ignore the exception.

My last advise will be ‘no exception should go completely silent, at least it should be logged for later analysis’.

You will find the in the sample C# project the various implementation of the cases exposes in this entry blog. Have fun with it.

References:

Advertisement
Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: