Exceptions


The CLR provides a structured mechanism, exceptions, for reporting and dealing with software errors. The fundamental idea underpinning exceptions is that when software detects a problem, it may communicate it to other software throughthe act of throwing an exception. Throwing an exception triggers behavior in the runtime to search for the nearest appropriate exception handler in the current call stack, or, if no handler was found (called an unhandled exception) the runtime takes over and gracefully terminates the program. Programs may use exceptions as an internal mechanism to deal with errors, or a public API might use them to communicate problems to its users.

Structured exceptions have been commonplace in environments such as C++ and Java for years and are quite similar in the runtime. Exceptions force resolution of each software error, whether that resolution is the user catching and dealing with it or the unhandled exception logic unwinding the execution of the program. This makes exceptions a great solution to problematic error codes, as used in the Win32 and COM platforms. In those platforms, it is all too common for users to forget to check the returned code for an error after each and every function call they make. This can lead to accidentally ignored errors, which often lead to data corruption and failures later on. Detecting and communicating an error as close to the problematic line of code that caused it as possible leads to a safer execution environment and a much-improved debugging experience.

Of course, the devil's in the details. Exceptions are simple in concept, but using them correctly takes care and experience. And there are some interesting facets and features of the CLR's exceptions subsystem that are not apparent up front. This section will discuss all of those details.

Exception Basics

A piece of code running in isolation is often uniquely qualified to identify when something is wrong. For example, a method might realize an argument wasn't in the correct format or was outside of the valid range, or an object might validate its state against a set of invariants, and realize it has encountered a situation it wasn't written to expect, or perhaps data corruption in the global environment has occurred that is beyond repair. In any case, the same programming construct can be used to communicate and deal with such a problem: that is, by throwing an exception. For very extreme cases of corruption, it might be prudent to perform a fail fast instead, which terminates the program immediately without carefully unwinding the stack. Both features are discussed in this section.

Reusable libraries use exceptions to indicate to callers that something unexpected occurred. For instance, a file API might throw an exception to indicate the file was not found, that the file is already open, or that the filename cannot be null. The former two are dynamic situations that the caller must be prepared to deal with, while the latter represents a program bug in the caller. In other words, the caller can't be expected to know up front whether the file was already opened, but it should definitely know that it's passing a null filename and respond accordingly before calling the file API.

Programs also use exceptions internally as a way to handle errors, usually dealing with them gracefully and only surfacing those that represent situations beyond its control. That is, in some cases a program will detect a failure, patch up the problem, and try again. In others, the program might let the exception go unhandled, enabling the runtime's default termination logic to reliably shut down the runtime and capture a crash dump. Many times, the software simply wasn't written to deal with such situations. In such cases, the user must be notified of the problem, and failing as quickly as possible enables developers to find the source of an error as close to the cause as possible. We will discuss unhandled exceptions in more details shortly.

An Example of the Exceptions Subsystem

Exceptions in the CLR are said to be thrown and can be caught by an enclosing exception handler. Finally blocks may be used to unconditionally execute code, whether as a result of normal or abnormal termination. Let's examine a brief example. The following C# code uses try/catch/finally blocks to inspect and propagate failures properly:

 public Customer LoadCustomer(int id) {     Customer c = null;     CustomerManager cm = new CustomerManager();     try     {         try         {             // We wrap this in a try block because it will throw an             // exception if the customer doesn't exist.             c = cm.Load(id);         }         catch (RecordNotFoundException e)         {             // An exception occurred. Maybe we can continue, for example             // by using a default database key. In many situations, we want             // to log the exception.             LogException(e); // user defined function             c = cm.Load(-1); // -1 represents the default record         }         catch         {             // This catches anything not of type RecordNotFoundException.             // If our program assumed only that type of exception could get             // thrown from our try block, we might want to log it for follow             // up. We might have a bug (or it may be an OutOfMemory, etc.).             LogProgramBug(); // user defined function             throw; // propagate exception         }     }     finally     {         // Finally guarantees that we will Close our Manager instance.         cm.Close();     }     return c; } 

There are many constructs utilized in this piece of code, including nested try blocks. The comments should explain clearly what this specific example does. We will take a look at each specific exception construct below.

Throwing

The IL instruction throw pops the object off the top of the stack and passes it to the exception propagation routine inside the CLR. The C# throw keyword — just like VB's Throw and C++/CLI's throw key-word (when compiled with the /clr switch) — is compiled into an IL throw statement. In other words, given the following C#:

 throw new Exception("Test exception"); 

This statement is compiled to the following IL:

 ldstr "Test exception" newobj instance void [mscorlib]System.Exception::.ctor(string) throw 

The CLR uses what is known as a two-pass exception model, a design choice made to interact seamlessly with Win32's Structured Exception Handling (SEH). Throwing an exception triggers the first pass, during which the search for an appropriate handler takes place, including executing any catch filters. Once it is determined whether a handler will catch the exception or whether the unhandled behavior will take over, the second pass takes place. In the second pass, the call stack is carefully unwound, and any active finally blocks executed. SEH is a complicated topic. We'll discuss that briefly in a few pages.

Many languages — such as C# for example — only permit programmers to throw objects of type System.Exception or of a derived type. Other languages, however, permit objects from other type hierarchies to be thrown, often without any type restrictions. Regardless of the restrictions a language imposes, the runtime only permits references to heap objects to be thrown. So if a value is thrown, the compiler must emit a box instruction first. Limiting the ability to throw to Exception-derived objects only is actually useful because this type holds detailed information about the call stack, representing the path an exception traveled through, along with a human readable error message, the former of which is maintained by the runtime and the latter of which is optional.

Note

Please refer to the section below "Exceptions Wrapping" for a description of new behavior in the CLR 2.0 that takes anything not deriving from Exception and wraps it. This helps to eliminate problems where non-Exception objects silently rip past a C# catch (Exception) {} block, among other things, such as ensuring there is always a stack trace available.

You of course needn't create a new object during the throw operation. You might cache a set of preconstructed exception objects, for example, although the above pattern is more commonplace.

Try/Catch Blocks

Managed code can catch exceptions that are thrown from within a try block. This gives the developer access to the error information, and enables him or her to respond to the condition appropriately. A catch block is usually written to deal with only a subset of all possible exceptions that might originate from within a block of code, typically identified by the type of exception thrown. Thus, the CLR provides ways to specify criteria that must hold true for a thrown exception to match the handler. These are called filters.

Note

Note that blocks are a language abstraction. When we look at the IL, you'll see that we really have try and catch regions identified and referred to via IL offsets instead of true lexical blocks.

When an exception occurs, the runtime responds by walking up its call stack to find an appropriate handler. This entails matching the type of exception thrown with type-filtered blocks and/or executing arbitrary filter predicates. As noted earlier, this is called the first pass. When a handler is found, the runtime transfers control from the throw site to the catch block, providing access to the exception object that was thrown (usually accessed through a named variable, e.g., as in C#). As the call stack unwinds to reach this point, that is, the second pass, any active finally blocks get executed.

There are two primary types of filters.

Catch on Type

The first and most common type of exception-handling criterion is filtering based on type. In this case, a catch block is tagged with a single CLR type. Any exception whose runtime type matches or derives from that type will be caught by the handler. For example, this block written in C# catches only exceptions derived from ArgumentException:

 void Foo() {     try     {         // Do something that can throw an exception...     }     catch (ArgumentException ae)     {         // (1) Do something with 'ae'     }     // (2) Code after the try block } 

That code causes the following to occur: if an object of type ArgumentException or one of its subtypes gets thrown — such as ArgumentNullException or ArgumentOutOfRangeException for instance — control would transfer to (1), and the variable ae would reference the exception that was thrown. Upon falling out of the catch block, execution transfers directly to (2).

In IL, ae in this example is simply a local slot in the method. The CLR delivers the exception to the catch block by pushing it on the top of the stack and once the catch block begins executing, the IL stores that exception so that it can be accessed further throughout the block. You can also execute the catch block without storing ae in this example, simply by omitting the variable name in the catch clause, for example as catch (ArgumentException) {/*...*/}.

This representation of try/catch blocks using lexical blocks in a language is nice. The CIL representation of protected regions (official name for try/catch blocks) is actually a bit different. The ildasm.exe tool permits you to view catch blocks in block-style to improve readability; this option is exposed through the View‡Expand try/catch menu item, and is turned on by default. But in IL and in the runtime's representation of methods each method holds a list of its protected regions. Each region has a begin- and end-instruction offset (the try region), along with a list of handlers (which also have begin and end indexes). The try block uses a leave instruction to exit upon normal completion.

For example, the above snippet of C# might look like this in IL:

 .method instance void Foo() cil managed {     .locals init ([0] class [mscorlib]System.ArgumentException ae) BEGIN_TRY:     ldarg.0     // Do something that can throw an exception...     leave.s AFTER_TRY BEGIN_CATCH:     stloc.0     // do something with 'ae' (ldloc.0) here     leave.s AFTER_TRY AFTER_TRY:     ret     .try BEGIN_TRY to BEGIN_CATCH         catch [mscorlib]System.ArgumentException handler         BEGIN_CATCH to AFTER_TRY } 

I've added labels to make this easier to follow; as discussed earlier in this chapter, compilers translate labels into relative offsets. If you were to view this same code in ildasm.exe with blocks intact, it would look as follows:

 .method private instance void Foo() cil managed {     .locals init ([0] class [mscorlib]System.ArgumentException ae)     .try     {         ldarg.0         // Do something that can throw an exception...         leave.s AFTER_TRY     }     catch [mscorlib]System.ArgumentException     {         stloc.0         // do something with 'ae' (ldloc.0) here         leave.s AFTER_TRY     }  // end handler AFTER_TRY:     ret } 

The latter is much nicer to read. Much as I use labels for illustrative purposes, I will use this format whenever showing IL catch blocks in this chapter.

Catch on Boolean Filter

Another type of exception handler criterion is filtering based on an arbitrary Boolean expression. C# doesn't expose this feature of the runtime, although VB does using its Catch...When syntax. This style of exception handling permits you to run a block of code at catch time to determine whether or not to handle a given exception. The filter is just an opaque block of code that leaves a value on the stack at its end to indicate whether it will handle the exception or not; it uses true (1) and false (0) to indicate this, respectively. If the answer is true, the corresponding handler block is given access to the exception and executed; if it was false, the runtime continues searching up the stack for a handler.

Note

Those familiar with SEH might expect a third value, -1. In SEH, this enables the catcher to restart the faulting instruction. Because restartable exceptions are difficult to write in even the lowest-level engineering scenarios, the CLR doesn't support this capability. Unmanaged code on the call stack can, of course, still make use of restartable exceptions.

The following code snippet demonstrates the use of a filter in VB. It calls a function ShouldCatch that returns a Boolean to determine whether to handle the exception. Any Boolean statement could have been used in its place:

 Function Foo()     Try         // Do something that can throw an exception...     Catch e As Exception When ShouldCatch(e)         ' Handle exception here     End Try End Function Function ShouldCatch(e As Exception) As Boolean     ' Perform some calculation     ' ...And return True or False to indicate whether to catch End Function 

In IL, the function F looks as follows:

 .method private instance object Foo() cil managed {     .locals init (class [mscorlib]System.Exception e) BEGIN_TRY:     ldarg.0     // Do something that can throw an exception...     leave.s AFTER_TRY BEGIN_FILTER:     stloc.0     ldloc.0     call instance bool ShouldCatch(class [mscorlib]System.Exception)     endfilter BEGIN_CATCH:     // do something with 'e' (ldloc.0) here     leave.s AFTER_TRY AFTER_TRY:     ret     .try BEGIN_TRY to BEGIN_FILTER         filter BEGIN_FILTER         handler BEGIN_CATCH to AFTER_TRY } 

Note that VB actually inserts some general bookkeeping overhead for exceptions that I've consciously omitted from the IL above. It logs and saves the last exception thrown in a global variable for reference.

Rethrowing an Exception

The IL instruction rethrow is similar to throw. It differs in that it preserves the existing call stack on the Exception object traveling through the catch blocks, tacking on the rethrow site to the stack trace. rethrow can only be used inside a catch handler, for example:

 void Foo() {     try     {         // Do something that can throw an exception...     }     catch (Exception e)     {         // do something with 'e'         throw;     } } 

Notice that C# doesn't use rethrow as its keyword as you might expect. Instead it hijacks the existing throw instruction, emitting the rethrow IL instruction when no arguments have been supplied.

A very easy mistake to make is to say throw e instead of just throw, which has the result of breaking the stack. In other words, it resets the stack trace such that it appears to have originated from the function Foo's catch block instead of inside the try block. When the exception originated from deep within a call stack inside the try block, the end result is an exception with very little information about the source of the error. This can make debugging extraordinarily difficult.

Fault Blocks

The runtime also has the notion of a fault block. These are much like catch blocks, except that they get triggered whenever any exception occurs (no filtering necessary). They do not gain access to the exception object itself. If an exception doesn't get thrown, the fault block does not execute.

No mainstream languages support faults directly, primarily because a fault can be simulated, for example in C#:

 try {     // Some code that might throw... } catch {     // "fault" block     throw; } 

This is implemented by the C# compiler as a catch block discriminated on type System.Object.

Finally Blocks

Developers often need a piece of code to run unconditionally upon exit of a try block, whether as a result of normal execution of the success path or due to an unhandled exception traveling outside of the block. A finally block does just that. The code inside of it is guaranteed to run whenever control transfers out of the try block, and after any relevant catch handlers in the block have been executed.

This is useful when working with resources, for example when you'd like to acquire a resource at the start of a block and ensure that it gets released at the end:

 // Acquire some system resource (or do the 1st of a paired set of operations): SomeResource sr = AcquireResource(); try {     // Use resource...an exception might occur in the block. } catch (SomethingHappenedException e) {     // Exception handling logic (if any, optional). } finally {     // Make sure we clean up the resource unconditionally:     sr.Close(); } 

You write this code without the catch block of course. Regardless of whether an exception is thrown or not, the sr.Close() statement will execute. It turns out the IDisposable pattern and C# using statement — described further in Chapter 5 — uses try and finally blocks in this manner to ensure reliable disposal of critical resources.

Nested try/catch/finally blocks execute as you might expect:

 try {     try { throw new Exception(); }     catch { /* (1) */ throw; }     finally { /* (2) */ } } catch { /* (3) */ throw; } finally { /* (4) */ } 

In this example, the inner try block's body throws an exception. This searches for and finds the catch handler (1), and transfers control to it; (1) just rethrows the exception, finds that (3) will catch it, executes the finally block in (2) and then enters the outer catch handler (3); this handler also rethrows, which goes unhandled and triggers execution of the finally block (4).

Finally Blocks Don't Always Execute

Under some circumstances, active finally blocks won't get a chance to execute. When the CLR shuts down, it makes no attempt to execute your finally blocks. Any cleanup logic must have been placed into a type's finalizer, which the CLR does give a chance to execute during shutdown. In addition to that, some sophisticated hosts (such as SQL Server, for example) carefully unload AppDomains to shut down isolated components. Ordinarily, your code will be given a chance to run finally blocks, but hosts often use a policy to decide when it is or is not appropriate to do so.

In any case, failure to run a finally block should not be catastrophic. We discuss finalization later on, but the consequence is that properly written code must ensure that resources whose lifetime spans a single AppDomain — especially those that span a single OS process — absolutely must release resources inside of a finalizer. They cannot rely entirely on finally blocks. Most components and finally blocks don't fall into this category. Critical resources should be cleaned up in critical finalizers to survive in some expedited shutdown situations. A primitive called SafeHandle has been added in 2.0 to aid in this task. Critical finalization is discussed in Chapter 11, where we cover related reliability topics.

Throwing Non-Exception Objects

The C# language (among others) steer people in the direction of the System.Exception class for all exceptions. For example, many users in C# will write the following code to mean "catch everything":

 try { /* some code that can throw */ } catch (Exception e) { /* handle exception */ } 

As we've already seen, however, a language can throw any object on the heap. The Common Language Specification (CLS) prohibits languages throwing non-Exception objects across language boundaries. But the simple fact is that there is no way to guarantee that you're working with a CLS-proper language — this goes entirely unchecked at runtime. The result is that calling an API which throws, say, a String might bypass a catch block filtered on Exception. After reading this chapter, this should be obvious, but many programmers are surprised by it.

Some consider this to be a C# problem. Isn't it C#, not the runtime, which forces people to think in terms of Exception-derived exception hierarchies? This debate, regardless of the feature, always involves a lot of gray matter. Certainly there is precedent that indicates throwing arbitrary objects is a fine thing for a language to do. Languages like C++ and Python have been doing it for years. And furthermore, C# actually enables you to fix this problem:

 try { /* some code that can throw */ } catch{ /* handle exception */ } 

But this solution has two undeniable problems. First, the catch-all handler doesn't expose to the programmer the exception that was thrown. Of course, C# could remedy this with new syntax or by stashing away the thrown object in some static variable. But there is an important problem that would still remain unsolved: non-Exception objects don't accumulate a stack trace as they pass up the stack, making them nearly impossible to debug.

Worst of all, the average programmer doesn't even know any of this is a problem! This problem affects several managed languages, and it really made sense for the runtime to help them out. Thus, a change was made in 2.0 to do just that.

Wrapped Exceptions

To solve the above problems, the CLR notices when a non-Exception object is thrown. It responds by instantiating a new System.Runtime.CompilerServices.RuntimeWrappedException, supplying the originally thrown object as the value for its WrappedException instance field, and propagates that instead.

This has some nice benefits. The C# user can continue writing catch (Exception) {} blocks, and — since RuntimeWrappedException derives from Exception — will receive anything thrown into their catch block. The try/catch block we had originally written will just work, and in addition to that, a full stack trace is captured, meaning that debugging and crash dumps are immediately much more useful.

Preserving the Old Behavior

Lastly, the runtime still permits languages to continue participating in throwing exceptions not derived from Exception that wish to do so. In fact, the default behavior of the runtime is to deliver the unwrapped exceptions catch blocks. The C# and VB compilers automatically add the System.Runtime.CompilerServices.RuntimeCompatibilityAttribute custom attribute to any assemblies they generate, setting the instance's WrapNonExceptionThrows property to true.

The runtime keys off of the presence of that attribute to determine whether the old or new behavior is desired. If the attribute is absent or present and WrapNonClsExceptions is set to false, the CLR wraps the exception as an implementation detail so that it still can maintain good stack traces for debugging. But it unwraps the exception as it evaluates catch handlers and filters. So, languages that don't wish to see the wrapped exception don't even know it ever existed.

For cross-language call stacks, the CLR performs the unwrapping based on whatever the assembly in which the catch clause lives wants to do. This means that a String can be thrown from an assembly with wrapping shut off and then catch it as a String. But if it throws a String out of a public API into code compiled where wrapping is on, that code will see it as a RuntimeWrappedException. If it is able to get past that code, any code it travels through sees whatever it expects.

Unhandled Exceptions

When an unhandled exception reaches the top stack frame on any managed thread in your program, it crashes in a controlled manner. Each managed thread has a CLR-controlled exception handler installed on the top of its stack, which takes over when an exception reaches it. This is a deviation from behavior in the pre-2.0 CLR, where only unhandled exceptions from the primary application thread would cause the program to terminate; unhandled exceptions thrown from an explicitly started thread, a thread in the thread pool, or the finalizer thread, would be silently backstopped by the runtime and printed to the console.

This often led to application reliability problems (e.g., hangs, crashes later on during exception), which are harder to debug due to the missing crash information, hence the change in 2.0.

Note

A compatibility switch is provided in cases where the new behavior causes 1.x applications to fail on the CLR 2.0. You simply place a <legacyUnhandledExceptionPolicy enabled="1" /> into the <runtime /> section of your application's configuration file. It is highly discouraged to build new applications which rely on this behavior. The configuration switch should be used as a migration tool until you are able to fix your application's unhandled exception bugs.

A few things occur during abrupt program termination resulting from an unhandled exception. If a debugger is already attached to the process, it is given a first chance to inspect and resolve the exception during the first pass. This occurs prior to any finally blocks being executed. Assuming the exception goes without interception the AppDomain.UnhandledException event gets triggered, and is supplied UnhandledExceptionEventArgs, which supplies the unhandled exception object. This is a good place to inject any custom program logic to perform things such as custom error logging.

After firing the event, the second pass takes over. The stack is unwound and finally blocks run, as is the case with exceptions handled higher up in the stack. The CLR handler inspects some registry keys to determine which debugging utility to launch during a crash. By default, it launches the Windows Error Reporting service. The user is then presented with a dialog choice that enables them to send this information over HTTPS to the secure server watson.microsoft.com.

Note

You might wonder where precisely that crash information goes. It is generally used by Microsoft to detect software errors caused by Microsoft's own software and has proven invaluable for diagnosing client-side bugs, which are hard to reproduce in a controlled environment. But other companies may also use the service to mine information about their own software worldwide. More information can be found at http://winqual.microsoft.com.

If a debugger is installed on the system — determined by inspecting the \HKLM\SOFTWARE\Microsoft\.NETFramework\DbgManagedDebugger registry string value — the dialog that appears gives a second chance to attach the debugger and interactively step through the problem or to capture a crash dump for offline analysis. Assuming that the user declines debugging (or if a debugger has not been installed on the system) but chooses to send the information to the Windows Error Reporting server, a minidump (brief summary of the program state at the time of the crash) is captured by the dumprep.exe program.

Note that the exact behavior is subject to change based on the host; what is described above pertains to unhosted scenarios only.

Undeniable Exceptions

When a thread is aborted — either via a call to System.Threading.Thread.Abort or as part of the AppDomain unload process — a System.Threading.ThreadAbortException is thrown in the target thread at its current instruction pointer. This exception can happen nearly anywhere. Furthermore, a thread abort is special in that it cannot be swallowed by a catch block. Exception handlers may catch it, but those which attempt to stop propagation will not succeed. The runtime ensures that the exception is reintroduced immediately following exit of any catch/finally blocks, almost as if the last line of the handler were a rethrow. This example code demonstrates this:

 try {     System.Threading.Thread.CurrentThread.Abort(); } catch (ThreadAbortException) {     // Do nothing here, trying to swallow the exception. } // When we get here, the exception is re-raised by the CLR. // Any code below won't get to execute... 

We synchronously abort our own thread in this example, which has the effect of throwing an undeniable ThreadAbortException. The catch block succeeds in catching it, but the exception is automatically reraised by the CLR once the catch block finishes. Thread aborts are discussed in more detail in Chapter 10 on threading.

The Exception Class Hierarchy

There is a type hierarchy in the Framework in which all commonly used exceptions live, the root of which is, of course, System.Exception. Exception supplies all of the basic information most exceptions need, such as a Message string and a StackTrace. It also provides the ability to wrap any other arbitrary exception as an InnerException and enables you to store a HRESULT for Win32 and COM error-code-mapping purposes.

All exceptions in the Framework derive from this class. We discussed above that, while it is legal to throw objects of types derived from other classes, it is rather uncommon. In addition to a common base type, nearly all exceptions in the Framework are serializable and implement the following constructors:

 [Serializable] class XxxException : Exception {     public XxxException();     public XxxException(string message);     public XxxException(string message, Exception innerException); } 

Of course, many exception types also choose to provide additional fields and thus might offer additional constructors. For instance, ArgumentException also stores the name of the parameter with which the exception is associated. This additional information makes debugging and displaying exception information much richer.

Below Exception in the type hierarchy are two major branches: ApplicationException and SystemException. For all intents and purposes, this hierarchy is deprecated, and the .NET Framework is not entirely consistent in following the original intent. The original idea was that application developers would place their custom exceptions under ApplicationException, and the Framework would put its exceptions under SystemException. Unfortunately, there are plenty of exceptions in the Framework that violate this policy, although deriving from SystemException is slightly more common, for example, in mscorlib.

Arguably, the only point of unifying exceptions with a common root is so that users can catch entire groups of exceptions without worrying about details when they are unimportant. For example, System.IO.IOException is a well-factored exception hierarchy; it has specific exceptions deriving from it such as FileNotFoundException, PathTooLongException, and the like. Users calling an API can catch a specific exception or just IOException. ArgumentException is similarly well factored.

When designing your own sets of exception types, consider those two hierarchies as great models to follow. However, it's doubtful that a single inheritance hierarchy will ever be rich enough to completely alleviate complex filters or catch-and-rethrows.

Fail Fast

Unhandled exceptions are a great way to communicate critical failures to other software components. But in some cases, ambient data has become so corrupt, or the environment's state so intolerable, that the program must terminate. Moving forward could cause more damage — such as committing corrupt state to disk — than losing the user's current work would. Throwing an exception does not guarantee the termination of the program. In fact, it's likely that other code would catch the exception, do some rollback or logging, and perhaps accidentally swallow it. This is always a possibility. If the consequences are so bad that you need to prevent this from happening, issuing a fail fast might be the best recourse.

A fail fast is executed by calling the void System.Environment.FailFast(string message) API. You pass it a description of the error condition as the message argument, and the CLR takes over from there:

 void SomeOperation() {     if (/*...*/)     {         // Something *very* bad happened. Fail fast immediately.         Environment.FailFast("...description of problem goes here...");     }     // ... } 

The result is similar to as if an unhandled ExecutionEngineException exception were thrown from the point of the FailFast invocation, although thread stacks are not carefully unwound. In other words, any attached debugger is still given a first chance, followed by a second chance for installed debuggers, and then a crash dump and Windows Error Reporting. But no catch or finally blocks are permitted to run. In addition to that, FailFast creates an Event Log entry containing the message supplied to the FailFast call.

A fail fast is always a critical bug in the software, and ideally a program would never issue one after it had been deployed. Clearly a fail fast in a publicly consumable API will lead to dissatisfied customers if it is constantly crashing their programs. That is not to say that you should conditionally compile calls to FailFast out of your production builds; they can prevent harmful damage to your user's data. But test coverage prior to shipping should eliminate the possibility of one occurring to as much extent as possible.

Two Pass Exceptions

Because the CLR must integrate nicely with mixed call stacks containing interleaved calls between unmanaged and managed code, compatibility with Structured Exception Handling (SEH) was very important to the architects of the CLR. Furthermore, building on top of SEH enhances debuggability of unhandled exceptions because the CLR can determine whether an exception will be caught before calling the debugger.

This decision, however, has some interesting and far-reaching consequences. Fortunately (and unfortunately at the same time), the tight-knit integration with SEH is hidden from you (unless you go looking for it), so most developers are entirely unaware of the possible complications it can add to their lives. In most cases, this is OK, but in others, it can cause headaches.

We've referred to passes in the discussion above, mostly to build up an understanding of the terminology. If you remember the difference between first and second pass, you're already halfway there to understanding SEH. In summary, there are two passes that occur when an exception is thrown:

  1. Any already attached debugger is given a first chance to handle the exception. If this is declined, the CLR crawls upward in the call stack in an attempt to locate a suitable exception handler. In the process of doing so it might execute opaque managed (or unmanaged!) code in the form of exception filters. At the end of the first pass, the CLR knows whether the exception will be handled — and if so, precisely where — or whether it must begin the unhandled exception behavior. The CLR installs an exception handler at the top of each thread's stack to ensure that it can handle exceptions instead of passing it to the OS.

  2. During the second pass, the CLR unwinds the stack consistent with what it discovered in the first pass. For example, if a handler was found, the unwind will stop at that method's frame and will transfer execution to its catch block. When a frame is popped off, any finally blocks attached to try blocks the call is lexically contained within get executed. An exception can go unhandled, which really means that the CLR's own top-level exception handler takes over. From the point of SEH, it actually did get handled by some opaque handler.

A subtle result of this behavior is that managed code can execute after an exception gets thrown but before the associated finally blocks are executed. In fact, if it weren't for the fact that C# doesn't support catch filters, it would be highly likely. (VB does, however.) In some cases, sensitive security state stored on the Thread Environment Block (TEB) might get installed before the try and rolled back inside finally blocks.

Impersonating a user on the current thread, for example, might look something like this:

 // Impersonate an administrator account... IntPtr administratorToken = /*...*/; WindowsImpersonationContext context =     WindowsIdentity.Impersonate(administratorToken); try {     // Do some protected operation under the impersonated context. } finally {     // Roll back the sensitive impersonation...     if (context != null)         context.Undo(); } 

Unfortunately, if the protected operation threw an exception, a filter could execute farther up the call stack while the impersonation token was still installed on the TEB. This might enable the filter to perform operations under the impersonated context, which could lead to an elevation of privilege-style attach. This happens because the finally block is executed after filters, as part of the second pass.

You can get around this little problem in one of several ways. You could repeat your finally block code in a catch-all block. Or you could write an outer try/catch block where the catch simply rethrows the exception. This will stop the first pass before reaching any callers that might have filters farther up the call stack. Or you could use the System.Runtime.CompilerServices.RuntimeHelpers type's ExecuteCodeWithGuaranteedCleanup method, which ensures that even in the most extreme situations your cleanup code gets executed before the second pass.

This code shows the second of the three solutions, while the others are left to your imagination:

 // Impersonate an administrator account... IntPtr administratorToken = /*...*/; WindowsImpersonationContext context =     WindowsIdentity.Impersonate(administratorToken); try {     try     {         // Do some protected operation under the impersonated context.     }     finally     {         // Roll back the sensitive impersonation...         if (context != null)             context.Undo();     } } catch {     throw; } 

This is admittedly an ugly trick. It forces the first pass to stop at the outer exception handler, then execute the inner finally block, and lastly jump to the catch block. The catch block simply propagates the exception, but not until after the impersonation has been successfully rolled back. This can in theory harm debuggability, for example if the finally block accidentally did something to mask the cause of the exception, but in practice it is likely to cause few developer problems.

Performance

Throwing and catching exceptions is more expensive than, say, dealing in numeric error codes. You can easily ruin the performance of hotspots in your application through an overreliance on exceptions for ordinary control flow purposes. In short: exceptions are for exceptional conditions only; a good rule of thumb is to avoid them when the error case will occur more than 10% of the time. Like all rules of thumb, take that number with a grain of salt and choose what is appropriate based on your own scenario, targets, and measurements.

If you stop to think about it, this fact shouldn't be overly surprising. When the exception subsystem is asked to perform its duty, you've ordinary performed a new managed object allocation (the Exception instance itself); it must then take this in hand, and interrogate all catch handlers up the call stack, sometimes involving invoking arbitrary code as is the case with filters. If it doesn't find a match, it needs to begin its error reporting routine, which can involve scouring memory among other things; the call stack is then walked, popping frames off one-by-one in a careful manner, executing finally blocks in the process. Returning an integer error code is clearly much less expensive, but for the reasons stated above, difficult to built robust software system on top of.

Watching Exceptions in Your Program

A number of performance counters exist to report back statistics from inside the CLR's exceptions subsystem. Namely, you can see things like total # of Exceptions Thrown, # of Exceptions Thrown/Second, # of Filters/Second, # of Finallys/Second, and Throw to Catch Depth/Second. You can turn these on and off using the Performance Monitor (perfmon.exe) Windows system utility.

A Case Study: Parse APIs

A great example of an inappropriate use of exceptions is the Parse APIs that were first shipped in v1.0 of the Framework. Nearly all fundamental CLR types, such as Boolean, Int32, Double, for example, have a Parse method that takes a string and parses it into an instance of the respective data type. Unfortunately, users often want to validate that the input is in the correct format before trying to parse it. When somebody enters data into a web site, for example, it's somewhat likely that they will have mistyped a number. Parse uses an exception to communicate incorrect input, meaning that you must write a catch handler simply to handle the common case where a string was in an incorrect format.

For instance, you might have to write the following:

 string qty = Request["Quantity"]; int parsedQty; try {     parsedQty = int.Parse(qty); } catch (FormatException) {     // Tell the user that 'qty' needs to be a number... } 

Not only is this annoying code to write any time you parse input, but it can harm performance, too. If we were in a tight loop parsing lots of numbers from a string-based source, for example, the cost of an occasional exception can wreak havoc on the performance of a single iteration. In v2.0, the Base Class Library (BCL) designers recognized that this was a case of exceptions being used for non-exceptional situations, and responded by introducing the new TryParse pattern and APIs:

 string qty = Request["Quantity"]; int parsedQty; if (!int.TryParse(qty, out parsedQty))     // tell the user that qty needs to be a number 

This pattern has been widely adopted throughout various nooks and crannies of the Framework. The convention is to use an output parameter to communicate the successfully parsed data and to return a Boolean to indicate success or failure of the parse operation.




Professional. NET Framework 2.0
Professional .NET Framework 2.0 (Programmer to Programmer)
ISBN: 0764571354
EAN: 2147483647
Year: N/A
Pages: 116
Authors: Joe Duffy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net