EDM Exception Use
Complete:
Goals of page
This page discusses throwing exceptions and handling exceptions in CMS software.
Where to find things
FWCore/Utilities/interface/Exception.h:
The main exception class cms::Exception is defined here.
FWCore/Utilities/interface/EDMException.h:
The edm::Exception class is defined here.
The
SWGuideEdmExceptionAnalysis page is a historical document that includes
design discussions related to the use of exceptions in CMS software. It is not completely
up to date.
Exception classes
The exception inheritance hierarchy is small:
edm::Exception --> cms::Exception --> std::exception
(where A --> B means "A is derived from B")
All other exception classes defined in CMSSW should inherit from
cms::Exception
if there is any possibility they will propagate to the Framework.
Most of the time, the best and easiest design is to use the
cms::Exception
class directly,
although defining new exception types derived from it is allowed and occasionally more
convenient.
cms::Exception
This is CMSSW's main exception class. The Framework can recognize information contained within this exception, add context or other information, print it out, and take an appropriate action. Any exception allowed to propagate from a processing module should be a
cms::Exception
or something derived from it. The action that the framework takes when one of these exceptions is caught is based on a category string given in the constructor.
Example use:
if(something wrong with data in the event)
throw cms::Exception("CorruptData")
<< "It seems as though something is dreadfully wrong.\n"
<< "Unknown ID " << x << " found\n";
else if(too much time)
throw cms::Exception("Timeout")
<< "Taking too long to process "
<< y << " number of hits\n";
The first argument passed to the constructor is the category. Category names should be short. The category name can be thought of as the general name of the problem. If you want to be able to configure a specific action for an exception or group of exceptions, then you should assign it a unique category (configuring actions is described below). Categories can also be useful for text based searches of log files when using a tool like 'grep'.
A category is always required when creating a
cms::Exception
.
Any object with a stream insertion operator (operator<<) can be appended to the message in a
cms::Exception
. A
cms::Exception
behaves like a std::ostringstream (it contains one). Alternatively, you can set the message by supplying the constructor
with a second argument, a std::string or char*.
throw cms::Exception ex("myCategory") << "My long message";
throw cms::Exception ex("myCategory", "My long message");
The only difference in the exception created by the above two lines is
that the constructor will insert a space at the end of the message if
it is not empty and does not already end in a space or newline.
There is also an append function that does the same thing
as the stream insertion operator.
cms::Exception ex("myCategory");
ex.append("My long message");
The interface above has existed since the beginning of CMSSW.
In CMSSW_4_3_0_pre3, additional features were added to the
cms::Exception class and the format of the string created by
the
what
function was modified. The interface described above
was not modified.
Here is an example of the output that will be printed when
a fatal exception occurs during a cmsRun process.
----- Begin Fatal Exception 04-Apr-2011 17:13:17 CDT-----------------------
An exception of category 'FileOpenError' occurred while
[0] Constructing the EventProcessor
[1] Constructing input source of type PoolSource
[2] Calling RootInputFileSequence::initFile()
[3] Calling StorageFactory::open()
[4] Calling File::sysopen()
Exception Message:
Input file file:dummy.root was not found, could not be opened, or is corrupted.
Additional Info:
[a] open() failed with system error 'No such file or directory' (error code 2)
----- End Fatal Exception -------------------------------------------------
The first and last lines are added by cmsRun and do not come from the exception itself.
These lines include the time and also the action (Fatal).
The rest is composed of four parts:
- One line that includes the category
- A numbered list of context strings
- The exception message
- Additional information, also a list of strings
If any of the four parts is empty in the exception, it will
not appear in the output. The category will always
be there. In a cmsRun process, the Framework will almost
always add some context information. The message
will be present if one was added. It usually will
be there, but does not have to be. We expect the
additional information will usually be empty. It is intended
to hold extra information that would be useful to experts
debugging a problem.
You can add a line of context or additional information
as follows:
cms::Exception ex("myCategory");
ex.addContext("Calling myClass::myFunction");
ex.addAdditionalInfo("Some detail about the bad thing that happened");
It is the design intent that the context string always start with an action
word like "Calling" or "Processing" or "Calculating", a word
that ends in "ing". The Framework will automatically add context
like the run number, luminosity block number, event number, path,
module class name, and module label. Please do not duplicate
this by adding the same context information at a lower level. The additional
information strings can contain anything.
In the printout both the context and additional information strings are
printed in reverse the order they were added.
edm::Exception
Exceptions that are generated from calls to framework functions (e.g. access to products in the event) are of this type.
This class uses an enumeration whose values correspond to particular category strings. An enumeration value
must be passed to the constructor and the corresponding string is automatically passed to the base class.
When an exception of this class occurs and it is fatal, the enumeration value is returned by cmsRun instead
of 0. When a fatal cms::Exception that is not an edm::Exception is thrown, the enumeration value
edm::Exception::OtherCMS = 8001 is always returned by cmsRun. The enumeration is defined in the
file FWCore/Utilities/interface/EDMException.h and can change from one release to the next,
although usually the change is to add new categories not modify the existing ones.
(At this point there are operational things that depend on the existing values so they
should not be modified at all.)
Exception Handling
Developers in general should not catch exceptions. Usually the best
design is to let the exception propagate to the Framework and let the Framework handle it.
There are cases where this rule does not apply:
- If an external package throws an exception that does not inherit from
cms::Exception
, then the module that invokes this external should catch it, then either deal with it or create a cms::Exception
, and throw that.
- There may be unusual cases where a module can handle a problem and the call stack between identifying a problem and dealing with it is deep enough that use of exceptions is reasonable.
- To add significant context information and rethrow.
- To modify the exception contents and rethrow.
- Test code.
Developers should throw cms::Exceptions whenever they
think they will not be able to perform the task they were called
to do (eg. produce an object to be put into the event).
This should be something that does not normally occur when
running a job properly. Exceptions should not be used like the
other control flow features of C++ (for loops, switch statements, break,
return values ...). They should be used for truly exceptional situations.
Exceptions should only be caught at a limited number of critical locations
to add context. For example, the code should
not be
cluttered with try-catch blocks to add context at every function call
to create a stack trace.
Framework Exception Handling
The framework is designed to handle exceptions that propagate
to it that are of type
cms::Exception
or some type that inherits
from that. It will catch and attempt to handle other types of
exceptions, but the result will not be as good. Information may be
lost or poorly presented, and without category information it will be
impossible to use the different configurable actions.
The Framework catches exceptions at many important places in its call
stack. These include but are not limited to
- code surrounding a call to a processing module
- the path executor
- the schedule executor
- the event loop
- the cmsRun application
- configuration validation
- module construction
By default, when an exception is thrown and propagates up to the Framework,
the Framework will catch the exception at multiple levels. At each level it will
add context information and rethrow the same exception until it reaches the
top level of the cmsRun application (in most cases, there are some special cases
that are different). At the top level, a message will be printed out that describes
the exception and the cmsRun process terminates and returns the exception
code.
As the exception is propagated up, there is an attempt to clean up things.
There is an attempt to call endLuminosityBlock methods, then
an attempt to call endRun methods, and then an attempt to close files.
If another exception is throw during one of these attempts, the Framework
gives up on that part of the cleanup and reports that another exception occurred
in the final exception message as additional information.
The default behavior described above is the "Rethrow" action used for "Fatal Exceptions".
It is possible to configure the Framework to take different actions based on
the category of the exception. The default action for all categories is "Rethrow".
Here are all the possible actions:
- Rethrow: Terminates the process with a non-zero return code
- SkipEvent: Stop further processing of this event and continue with the next event
- FailPath: Stop processing in the path and mark it as failed, and continue with the next path
- IgnoreCompletely: Continue with the next module on the current path
These actions apply for exceptions thrown while a module (e.g. an EDProducer, EDFilter,
EDAnalyzer, or OutputModule) is processing an event. Exceptions thrown at other times,
such as when processing a beginRun or endRun, always result in a Rethrow action.
The above actions occur as stated if thrown during module execution on a path (as opposed to an endpath).
If thrown while executing a module on an endpath, FailPath or SkipEvent is treated as IgnoreCompletely,
so that other modules on the endpath are unaffected. There are more details related to this
explained here:
The_options_Parameter_Set
Here is an example of how one would configure the the SkipEvent
action for modules with labels A, B, and C and the IgnoreCompletely
action for the module with label Q
process.options = cms.untracked.PSet(
SkipEvent = cms.untracked.vstring( 'A', 'B', 'C' ),
IgnoreCompletely = cms.untracked.vstring( 'Q' )
)
Exception Handling Outside the Framework
To repeat what was said above,
developers in general should not catch exceptions.
Usually the best design is to let the exception propagate to the Framework and let the Framework handle it.
But there are cases where exceptions are and should be caught outside the Framework
and some guidance follows related to that. This guidance also applies to exception
handling in the Framework.
All exceptions should be caught by reference. It should be
a non-const reference if adding context or otherwise modifying
the exception. Here is an example.
catch (cms::Exception& iException) {
std::ostringstream ost;
ost << "Constructing input source of type " << modtype;
iException.addContext(ost.str());
throw;
}
Avoid the following because it might slice an exception
and involves an unnecessary copy.
catch (cms::Exception& iException) {
...
throw iException;
}
If you need to change the type of an exception or
change the category, then you will need to create
a new exception and throw it. Here is an example:
catch (cms::Exception const& e) {
edm::Exception ex(edm::errors::FileOpenError, "", e);
....
throw ex;
}
Note that the 3 argument constructor exists for both cms::Exception
and edm::Exception. The first argument is the category,
a string or const* for cms::Exception or an enum value for
an edm::Exception. The second argument is text that goes
directly into the exception message. It starts the new message.
If the text is not empty and does not end in a newline, then a
newline is appended to it. The third argument is another cms::Exception. It's
message is appended to the new message. Also its list of context strings
and its list of additional information strings are copied into the
new exception.
If the category and type of the exception are not changed, then
it should not be necessary to create a new exception.
There are functions that allow modifying the message, modifying
the context list, and modifying the additional information. Here are
the modifier functions:
void append(Exception const& another);
void append(std::string const& more_information);
void append(char const* more_information);
void clearMessage();
void clearContext();
void clearAdditionalInfo();
void addContext(std::string const& context);
void addContext(char const* context);
void addAdditionalInfo(std::string const& info);
void addAdditionalInfo(char const* info);
void setContext(std::list<std::string> const& context);
void setAdditionalInfo(std::list<std::string> const& info);
The three
append
functions modify only the message (not the
context or additional information). The one that takes an argument
that is another exception appends only the message
to the message, it does not use or modify the context or
additional information lists. The behavior of the other
functions should be obvious from the function name
and arguments. Their behavior is straightforward.
The
operator<<
function can also be used to modify
the message as described above.
One can use the following to access the contents of
an exception.
std::string const& category() const;
std::string message() const;
std::list<std::string> const& context() const;
std::list<std::string> const& additionalInfo() const;
int returnCode() const;
If you have test code and catch an exception and want
to print out the entire formatted exception message that
includes the context and additional information, then there
are three equivalent ways to do it:
catch (cms::Exception & ex) {
std::cerr << ex;
std::cerr << ex.what();
std::cerr << ex.explainSelf();
One quirk exists. You probably do not want to do
the following because the format of the message will
look strange:
catch (cms::Exception const& e) {
edm::Exception ex(edm::errors::FileOpenError);
ex << e;
// this is equivalent to
// ex << e.what();
throw ex;
}
Finally, the cms::Exception class has clone and raise methods.
These are virtual functions that are designed to behave properly
with classes that inherit from cms::Exception and also define
them. They should be used to make copies or throw
when all you have is a base class reference to avoid slicing.
Review Status
Responsible: Main.jbk
Last reviewed by:
DavidDagenhart - 8 December 2011