Exceptions
Introduction
This page contains some preliminary rough notes about using exceptions and assets in the Mu2e Offline.
The exception handling policy is similar in spirit to that used by CMS, although some technical details differ. The CMS policy can be found at in the CMS guide to managing exceptions .
An important part of the policy is that you should prefer throw over assert in most cases. One critical reason is that, for our production code, the compiler switches are configured to ignore asserts.
Using Exceptions
Mu2e code should only throw exceptions when it intends that art should catch the exception. Mu2e code should not use try/catch blocks to implement normal flow control; this policy should only be violated if one is using a third party product that supports only try/catch for normal flow control.
When Mu2e code throws an exception, the normal syntax is
#include "cetlib/exception.h" if ( something_bad ) { throw cet::exception("CATEGORY") << "Some useful text.\n"; }
The string "CATEGORY" is called the category of the exception; it is used by art to decide what action to take in response to the exception. Mu2e will define some standard categories and encourage their use; for now we are making them up as we go along. The text added with operator<< is arbitrary and may be many lines long. Art will decorate the text with information about what module or service was executing at the time of the exception and will add a time stamp. The exception handling code ignores std::endl but not "\n".
In response to an exception art can perform several different actions:
Rethrow: | Shutdown the job as gracefully as possible and terminate the job with non-zero return code. |
SkipEvent: | Continue processing with the next event. |
FailPath: | Continue processing with the next path. |
FailModule: | Continue processing with the next module in the current path |
IgnoreCompletely: | If possible, continue as if nothing happened |
When an exception is rethrown, art will, if possible, call the appropriate the end* methods for all
modules and services. Normally this results in a proper shutdown, with the ROOT output file properly
closed, with all event-data files properly closed and with all message streams flushed.
In addition, it is possible to route events that throw exceptions to dedicated output files; one could, for example, configure an output module to write out only events that failed a given path.
When art catches an exception, it consults an run-time-configurable policy for which of the above actions it should take; the policy is stated by saying listing those exeception categories that should Rethrow, those that should SkipEvent and so on. By default almost all categories are configured to rethrow. For a handful of categories, the default is SkipEvent; in particular the "data product not found" exception defaults to SkipEvent.
There are four ways to change the configuration of which exception category has which behaviour:
- The command line switch --rethrow-default will make the default setting for all exceptions be Rethrow.
- An equivalent action can be done in the .fcl file, services : { scheduler : { defaultExceptions : false } }
- (The information in this bullet has not yet been tested). One can configure the behaviour on a detailed, category by category basis using a syntax like: services : { scheduler : { defaultExceptions : false SkipEvent : [ "CategoryName1", "CategoryName2", "CategoryName3" ] } } The fcl parameters for the actions are named IgnoreAll, SkipEvent, FailModule, FailPath and Rethrow. (What is the precedence of the .fcl file settings and the --rethrow-default?)
- The command line option --rethrow-all will ingore the settings in the .fcl file and will set all categories to rethrow.
Why does Mu2e discourage the use of try/catch blocks for normal flow control? Mu2e does not want to pay the run-time
costs to establish and tear down the exception handlers; while this would not be a problem if used in appropriate places,
the experience of other experiments is that inexperienced programmers quickly adopt the try/catch model and use it in inappropriate
places.
Prefer throw to assert
Mu2e strongly discourages the use of C++ assert to deal with most exceptional circumstances.
- Mu2e production code is built using -DNDEBUG, which tells the compiler to ignore asserts. If you intend your test to protect production code, you must use throw.
- When an assert is triggered, execution stops immediately, the output streams are not flushed and the ouptut files are not properly closed. In particular, the log file will be incomplete and you will not be able to view the histograms that were filled before the error.
- When you throw an exception, art will catch the exception and, by default, will attempt a graceful shutdown. In most cases the graceful shutdown will complete correctly. In these cases the log files will be complete. Also, the histograms, TTrees and other objects in the ROOT root output file will be correct and readable up to the time of the throw. Remember that other people's modules may be running in the same job and that their modules may have useful output, even if your's does not.
- When it comes time to write code that will run in the online event filter (AKA the software trigger ) there may be additional guidelines coming from the Trigger/DAQ team.
You should understand that assert is only useful for debug builds. And it should be reserved for situations in which the program is so badly broken that it does not make sense to attempt a graceful shutdown.