EDM Exception Use

Complete: 5

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

Reviewer/Editor and Date (copy from screen) Comments
Main.jbk - 02 Sep 2006 page author
JennyWilliams - 31 Jan 2007 editing to include in SWGuide
Main.wmtan - 09 Oct 2008 bring up to date
ChrisDJones - 12-Nov-2009 switched to python config language
DavidDagenhart - 8-Apr-2011 rewrite after major modifications were made to exception class

Responsible: Main.jbk
Last reviewed by: DavidDagenhart - 8 December 2011

Edit | Attach | Watch | Print version | History: r30 < r29 < r28 < r27 < r26 | Backlinks | Raw View | WYSIWYG | More topic actions
Topic revision: r30 - 2011-12-08 - DavidDagenhart



 
    • Cern Search Icon Cern Search
    • TWiki Search Icon TWiki Search
    • Google Search Icon Google Search

    CMSPublic All webs login

This site is powered by the TWiki collaboration platform Powered by PerlCopyright &© 2008-2023 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
or Ideas, requests, problems regarding TWiki? use Discourse or Send feedback