4

I'm writing a general discrete event system simulation library in C#. I'm going to write another library on top of that which will implement a specific kind of discrete event sim. Here is an integral version of the code.

static class Engine
{
    [ThreadStatic] internal static uint time;
    //...

    public static void Run(OnException onException = OnException.PauseAndRethrow,
                           IList<Type> exceptionsToPass = null)
    {
        //...
        while (!stop)
        {
            Event e = currentTimeEventQueue.PopEvent();
            if (e == null) break;
            try {
                e.Activate();
            }
            catch (EngineException ex)
            {
                // An engine method that shouldn't have been called
                // was called, and Activate didn't handle that

                // handle this situation...
            }
            catch (/* fatal exception type*/ ex)
            {
                throw;
            }
            catch (Exception ex)
            {
                // code to decides whether to dismiss exception
                // pause, stop, reset based on parameters to this method
            }
        }
    }
}

The question is: Should I specifically catch Exception types that are known to be unrecoverable (that I shouldn't try to handle in any way). What are those Exceptions (I can think of OutOfMemoryException and StackOverflowException). Is there a list of fatal Exceptions? As I remember some of them are uncatchable. So I am interested in a list of fatal Exceptions that can be caught. I just want to rethrow them, and not do anything. On the other hand I want to handle any other type of Exception. Or maybe I need another angle on this.


EDIT: OK, I've made a huge oversight when writing the question. Activate() is abstract. I am writing a general purpose discrete event system simulation library. The engine is working with totally unknown subclasses of Event. So it is calling an absolutely unknown method Activate(), that could throw any kind of exception. I could just ignore this issue, but I want to give control of the process to the caller. As you can see from the parameters to the Run() method, the caller decides what the engine will do if an exception comes from a call to Activate() (it can instruct the engine to ignore and continue, or pause and rethrow, or ...). That is why I am trying to separate fatal from all other Exceptions. If the caller has instructed the engine to ignore any exception that comes from Activate() it would be unwise to catch and ignore fatal exceptions. (there be dragons :) )

jdhurst
  • 4,365
  • 1
  • 20
  • 21
irpbc
  • 851
  • 1
  • 8
  • 16
  • possible duplicate of [Which types of exception not to catch?](http://stackoverflow.com/questions/5507836/which-types-of-exception-not-to-catch) – Hans Passant Jan 07 '13 at 18:33
  • I also want to catch by severity and resumability. In the end I do of course care why a component failed but that information is not important for deciding where in the code to catch. The more severe an exception, the further up in the call stack I want to catch it. In the most fatal cases catch in main, log and exit. I would offer a bounty if I would have hope that this is possible with the dotNet exception hierarchy. – Patrick Fromberg Apr 08 '14 at 20:01

3 Answers3

3

Should I specifically catch Exception types that are known to be unrecoverable

No. You shouldn't. If they are unrecoverable you shouldn't try to recover from them.

The rule regarding exceptions is - catch and handle exceptions that you know how to recover from. Let any other bubble up - if this means an application crash, this is probably for the best.

The following is a code smell and shouldn't be coded:

catch (/* fatal exception type*/ ex)
{
    throw;
}
Oded
  • 489,969
  • 99
  • 883
  • 1,009
  • But if the middle catch block is not present, catchable fatal exceptions will end up in the last catch block, and any handling code in there could fail unpredictably. (I think) – irpbc Jan 07 '13 at 18:39
  • 1
    @iRasic - Which brings me to the same point in the question. Don't catch exceptions you don't know how to handle. – Oded Jan 07 '13 at 18:42
  • In this case, I actualy know how to handle any exception, except fatal exceptions. – irpbc Jan 07 '13 at 18:53
  • 1
    @iRasic - Then don't catch them. Let the bubble up and terminate the application. – Oded Jan 07 '13 at 18:57
  • @irpbc I think Oded's point is that rather than catch all and filter out the fatal ones, you should explicitly catch the non-fatal ones. Yes, that means writing a separate catch block for each exception type that you want to handle. – Ian Goldby Oct 11 '16 at 10:18
2

No throwable is really "uncatchable"; anything that can be thrown in .NET derives from Exception and so can be caught. However, many things shouldn't be caught. Hence your question; how to tell the difference?

The general rule I follow is "Catch the exceptions that you expect and know how to deal with". This requires you to first know what your code could throw. The MSDN docs are usually pretty good at stating what various framework methods will throw in what situations; your own codebase is likely less well-documented unless you're developing an intermediate library to be consumed by other coders (or your management/leads are anal about proper documentation).

Once you know what the code can throw, determine what, if anything, you should catch. Exception trapping, aka "Pokemon handling" (Gotta catch 'em all) is generally a bad thing because there are legitimate reasons to let your application die a fiery death and make the user restart it. Examples include StackOverflowExceptions, OutOfMemoryExceptions, and various Win32Exceptions detailing some internal failure to provide your program with the requested resources. You usually cannot recover from these in any meaningful way.

Most errors, however, are not that serious. Exceptions are thrown when a file isn't found, or when a network connection is refused or unexpectedly closed, or when you attempt to do something with a type without checking for null (and what happens when the check for null fails? Often you can't continue and must throw an exception of your own). These are things that you should be expecting, catching, and at the very least communicating to the end user in some understandable way.

In many cases, try/throw/catch is a useful tool for expected but not everyday situations; if you receive a timeout exception while waiting for results when usually there's no problem getting results, then don't even tell the user there was a problem; just try again. If you can (should?) plug in some default value when a calculation can't be evaluated (divide by zero, math that could produce non-real results but the code can't handle them, etc etc), then do so.

There is, however, one scenario I can think of where you simply must catch and rethrow, and that's in situations involving database transactions. If you're performing a large "unit of work" with a database transaction handling several persistence operations along the way, then if anything goes wrong, you should roll back the DB transaction. In that case, the operation should be surrounded in a try-catch(Exception) block that catches the exception, rolls back the transaction, and rethrows. Any exception that you can proceed through normally should be handled with a nested try-catch or the condition should be checked for prior to the operation that could fail in that way.

KeithS
  • 70,210
  • 21
  • 112
  • 164
  • I've edited the question. (I failed to write the whole story the first time) – irpbc Jan 07 '13 at 23:04
  • 1
    I think my answer stands; you must still know what the code *could* throw, so when an exception *is* thrown you can catch and then decide to swallow or rethrow it. There are, as I said, certain exceptions you simply should never try to suppress; SOE, OOME and most Win32Exceptions with a message containing "Insufficient". Beyond that, whether something is "fatal" is really more your call, and you should make it based on, "do I know what to do to recover from this error and continue?" – KeithS Jan 07 '13 at 23:19
  • Good point, and I should be giving some responsibility to the caller. – irpbc Jan 07 '13 at 23:34
  • For the case you mention (ie rolling back a db transaction on thrown exception), the correct way to do this (and indeed to handle any resource cleanup) is to use a try/finally block. There's no need to catch and rethrow the exception (though I'll sometimes do this in order to get localised logging). – River Satya Oct 21 '15 at 03:36
  • Depends on the system and its configuration. I've worked with a few databases where there isn't one thing or even a set of things you can do in a finally block to both roll back and clean up. My usual pattern is a using block for the transaction (or its abstraction such as a "Unit of Work"), with an additional try block for the DB code with a commit as the last line; if any exception is thrown in the try block, a rollback is performed in the catch whether or not the exception is rethrown or handled there, then at the exit of the using block the transaction is disposed of. – KeithS Oct 21 '15 at 16:05
  • Good reasons for doing it my way might include a Unit of Work mechanism that doesn't support using the same instance for multiple transactions and therefore would error if you tried to rollback after a commit. NHibernate is also picky about flushing the session after an exception, which is a common cleanup step but one you don't want to do in a finally block if you got there by throwing an exception. – KeithS Oct 21 '15 at 16:11
1

Your question is a reasonable one to ask, but alas there is often no really good answer. A major weakness with the exception paradigm in C++, which unfortunately follows through into other languages and frameworks which borrow from it, is that it encapsulates way too much in the type of an exception object. In C++, this is understandable, since Bjarne Stroustrup wanted to avoid having any non-primitive types be "hard-coded" into the language; given that constraint, the C++ design may have been the best possible. Nonetheless, such a design imposes some serious limitations.

The biggest problem is that code which is considering catching and processing an exception, and then either swallowing or rethrowing it, may be interested in a number of things:

  1. What happened that prevented code from behaving as expected
  2. What actions should be taken in response to the condition
  3. Once various actions have been taken, should the condition be considered "resolved"
  4. Should one adjust one's assumptions about any aspects of the system state beyond what is implied by the method's failing (e.g. if file "foo/bar" couldn't be read, the nature of the failure may impact a decision whether to try reading "foo/boz").

In Java and .net, the expectation is that the exception type should be used to indicate #1 above, and there's no standard means for exceptions to answer #2 and #3, even though they are in many cases far more important than "what happened". If one tries to load a document and something goes wrong, 99% of the time the system will be in the same state as it was before the load attempt, so the exception, regardless of its type, will basically mean "file isn't readable and wan't loaded". Further, in that remaining 1% of the time where something "bad" happens beyond the failure to load the file, it's entirely possible that the exception type will be the same as one that's thrown in the 99%.

Absent some new exception-handling features, one's best bet is probably, to the extent possible, have exceptions that indicate state corruption invalidate the corrupted parts of system state such that all future operations on those parts will likewise throw exceptions. Such behavior will mean that exceptions that code can't handle will end up bringing down the system (since the code will keep trying to use parts of the state that have become corrupted), while allowing code to recover from things it should recover from (e.g. if an exception occurs because of corruption in an object which was being built, and if the exception causes code to abandon the object under construction, such abandonment would suffice to "handle" the exception, and so execution can and should continue).

supercat
  • 77,689
  • 9
  • 166
  • 211