Error Handling

From DocR23

Jump to: navigation, search

Error Handling transfers the control of the program from one place to another if unwanted results occur or a user interrupts the program. ACIS handles all standard errors that occur while executing a program. It also provides procedures to add new error types and produce warnings at various stages of program execution.

Contents

Exception Types

See also: ACIS Error Messages
  • Fatal errors are those from which there is no immediate recovery. A fatal error is one that causes the API function to fail and roll back to its initial state.
  • Errors that are unexpected events, but are not immediately fatal, are warnings.

The same event is a fatal error under some circumstances, unexpected but nonfatal under other circumstances, and acceptable under other circumstances. Regardless of whether a given event is fatal, the same error code is used. The function used with that error code informs the system whether the error was fatal.

Error Handling Functions and Macros

The following error handling functions and macros are used by ACIS to catch and control errors. Applications may also need to use these when using ACIS Direct Interface functions or class methods (that is, non-API functions). The functions and macros are listed in the order of execution:

Error Signaling Functions

Table. Error Signaling Functions
Function Description
error_begin Establishes ACIS signal handling. Each call to error_begin must be offset by a corresponding call to error_end. Calls to these two routines may be nested. The outermost call to error_begin establishes signal handlers, resets the warning count, and resets the error hardness level. Each call to error_begin increments the error level.
error_harden Inhibits processing of user interrupts. Each call to error_harden increments the error hardness level. User interrupts temporarily ignored while the error hardness level is greater than zero. All other signals and errors are processed normally.
error_soften Enables processing of user interrupts. Each call to error_soften decrements the error hardness level. When the error hardness level reaches zero, any user interrupt that was ignored is processed.
error_end Resets signal handling. Each call to error_end decrements the error level. When the error level reaches zero, error_end resets the signal handlers to those that were in effect when the corresponding call to error_begin was made. If a user interrupt was seen, The application's interrupt signal handler is called.
sys_error Signals ACIS errors and interrupts. The errors reported using this function are fatal errors. If the crash option is on, the function causes a core dump. Otherwise, it transfers control to the innermost active ERROR_BEGIN macro, setting its error_no to the specified error code.
Note: There are multiple signatures of sys_error.
sys_warning Reports unexpected, nonfatal events that occurred during the course of the execution. It saves the error code given as input in an err_mess_type global array. Like sys_error, this routine checks for the crash option. If the option is on, it prints the message associated with the current error code. Otherwise, the application must process this warning.

Exception Handling Macros

Four macros have been defined to allow ACIS code to take corrective action if an error or interrupt occurs. Applications may also need to use these when using the ACIS direct function or class interfaces (that is, non-APIs). The four exception handling macros are:

Table. Error Handling Macros
Macro Description
EXCEPTION_BEGIN Defines the beginning of an exception handling block, which allows the declaration of local variables that must be cleared should an error occur.
EXCEPTION_TRY Defines the beginning of a try/catch block, which denotes the beginning of the block that contains the normal processing code.
EXCEPTION_CATCH ... Defines the termination of the try/catch block, which denotes the beginning of the block that contains the code that handles the exception.
EXCEPTION_END ... Defines the termination of the catch block, with the option of re-signaling the error.

Usage

EXCEPTION_BEGIN
    // Declarations of local variables to be cleaned up go here
EXCEPTION_TRY
    // Normal processing code goes here
EXCEPTION_CATCH(always_clean)
    // Interrupt/error cleanup code goes here
EXCEPTION_END

The macros must appear in the order specified above (BEGIN, TRY, CATCH, END). The EXCEPTION_CATCH macro is optional and may be omitted if only local variables are to be cleaned up. Sets of macros may be nested, but they must not overlap. The inner set must be fully contained between adjacent macros (normally between EXCEPTION_TRY and EXCEPTION_CATCH) of the outer set.

In addition to the four macros listed above, three alternative macros exist:

EXCEPTION_CATCH_FALSE
    // Logically the same as EXCEPTION_CATCH(FALSE)
    // but it does not cause a compiler warning about 
    // using a constant expression in an if statement.
EXCEPTION_CATCH_TRUE
    // Logically the same as EXCEPTION_CATCH(TRUE)
    // but it does not cause a compiler warning about 
    // using a constant expression in an if statement.
EXCEPTION_END_NO_RESIGNAL
    // Logically the same as setting resignal_no = 0;

You must select zero or one of the CATCH macros and exactly one of the END macros in each exception block. For example, you should never have an EXCEPTION_CATCH_TRUE and an EXCEPTION_CATCH_FALSE in the same block of protected code.

The macros are defined in errorbase.hxx which is included in all ACIS .err files. If a file currently calls sys_error no additional header files should be needed. Otherwise, errorbase.hxx should be included.

The EXCEPTION_BEGIN block (the lines of code between the EXCEPTION_BEGIN and EXCEPTION_TRY macros) is used to declare variables that must be cleaned up if an error occurs. This includes pointers to be deleted in the EXCEPTION_CATCH block as well as local instances of classes that may contain pointers to potentially large amounts of dynamically allocated memory. Local instances are automatically destroyed by block exit code in the EXCEPTION_END macro before any error is re-signaled.

Variables declared in the EXCEPTION_BEGIN block may be declared as being volatile so that they will not be created as register variables, as shown below. This will prevent the unexpected resetting of the variables during exception handling. For more information on the volatile type qualifier, refer to HowTo:Use the volatile type qualifier.

EXCEPTION_BEGIN
  myclass* volatile vm = NULL;
EXCEPTION_TRY
  vm = new myclass( /*... */);
  //...
EXCEPTION_CATCH(TRUE)
  delete vm;
EXCEPTION_END

If multiple variables are declared in the EXCEPTION_BEGIN block, their initialization should be kept as simple as possible since any error or interrupt that occurs in this block will not cause the corresponding EXCEPTION_CATCH block to be executed nor the local variables to be destroyed.

The EXCEPTION_TRY block (the lines of code between the EXCEPTION_TRY and EXCEPTION_CATCH macros) contains the normal processing code. Variables declared here are visible only within the EXCEPTION_TRY block. They are destroyed by the block exit code only if no error or interrupt occurs. Variables that need to be cleaned up if an error occurs should be declared in or before the EXCEPTION_BEGIN block. Variables that must be visible after the EXCEPTION_END macro must be declared before the EXCEPTION_BEGIN macro.

The EXCEPTION_CATCH block (the lines of code between the EXCEPTION_CATCH and EXCEPTION_END macros) is used to free dynamically allocated memory and reset global variables. The always_clean argument to the EXCEPTION_CATCH macro is a logical expression used to indicate whether the EXCEPTION_CATCH block should be executed even if no error occurs. This is useful for avoiding the duplication of code used to free temporary memory. The variable error_no can be examined to determine what (if any) error occurred. The variable resignal_no can be modified to change the error re-signaled to higher blocks. Setting resignal_no to zero stops the error from being re-signaled. This can also be accomplished by using the EXCEPTION_END_NO_RESIGNAL macro instead of EXCEPTION_END.

ACIS error handling uses C++ try/catch statements and only throws and catches exceptions of a specific internal type. Asynchronous exceptions, such as access violations and floating point exceptions, are caught in platform specific ways but are ultimately rethrown (resignaled) with sys_error.

Macro Example

The following example demonstrates how the EXCEPTION_CATCH_TRUE macro can be used to always clean up allocated memory. In this case it is used to delete the dbls array.

Table. Exception Handling Macros - Example
Without Exception Handling With Exception Handling
// Local instance with dynamic memory
ENTITY_LIST list;
 
//...
// ENTITY handled by BB mechanism
EDGE *ent = new EDGE(...);
list.add(ent);
//...
// Temporary memory
double *dbls = new double[n];
//...
delete [] dbls;
EXCEPTION_BEGIN
  ENTITY_LIST list;
  double *dbls = NULL;
EXCEPTION_TRY
  //...
  EDGE *ent = new EDGE(...);
  list.add(ent);
  //...
  dbls = new double[n];
  //...
EXCEPTION_CATCH(TRUE)
  delete [] dbls;
EXCEPTION_END

Guidelines for Exception Handling Implementation

The following topics are some guidelines for implementing exception handling for memory cleanup. These guidelines cover some of the most common situations involved in memory cleanup. There are bound to be other situations that are not covered here. These are only guidelines and individual circumstances may be better handled in other ways.

Instances of ENTITY

Instances of classes derived from ENTITY and ACIS objects pointed to by them do not need to be cleaned up. The history mechanism will take care of them. Deallocating ACIS objects pointed to by ENTITY class instances will almost certainly cause memory access errors.

Functions Returning Pointers

Simply looking for occurrences of new and delete will not identify all places where exception handling needs to be implemented. Functions that return pointers to allocated memory and instances of classes (such as ENTITY_LIST) that may contain pointers to large amounts of allocated memory should also be identified.

// Local instance of memory consuming class
ENTITY_LIST list;
// Function returns allocated memory 
curve_curve_int *cci = int_cur_cur(c_1, c_2);

Declaration of Local Instances

Declarations of local instances of classes that may contain pointers to large amounts of dynamically allocated memory that would be freed by their destructors should be moved into a EXCEPTION_BEGIN block.

Freeing Memory when Exceptions Occur

All memory that the routine would normally free upon successful completion should be freed if an exception occurs.

Memory that Needs Freeing Correct Handling
FOO *foo_array = new FOO[n];
//...
delete [] foo_array;
EXCEPTION_BEGIN
  FOO *foo_array = NULL;
EXCEPTION_TRY
  foo_array = new FOO[n];
  //...
EXCEPTION_CATCH(TRUE)
  delete [] foo_array;
EXCEPTION_END

This becomes slightly more complicated if some memory always needs to be freed and some memory needs to be freed only in the event of an exception. The following example demonstrates how the error_no variable can be used to achieve this.

EXCEPTION_BEGIN
  FOO *foo_array1 = NULL;
  FOO *foo_array2 = NULL;
EXCEPTION_TRY
  foo_array1 = new FOO[n];
  foo_array2 = new FOO[n];
  //...
EXCEPTION_CATCH(TRUE)
  delete [] foo_array1;  // This is always freed.
  if (error_no) {
    delete [] foo_array2;  // This is freed only in the event of an exception.
  }
EXCEPTION_END

Avoid Multiple Deletion

Memory that would be returned or attached to another object should be cleaned up in the EXCEPTION_CATCH block if it could exist for a significant period of time before being returned or attached. Avoid multiple deletion of allocated memory pointed to by class instances to be cleaned up. In the following example the instance of the FOO object is owned by the instance of the BAR object.

Multiple Deletion Correct Handling
FOO *foo_ptr = new FOO(...);
...
BAR *bar_ptr = new BAR(foo_ptr,...);
...
return bar_ptr;
BAR *bar_ptr = NULL;
EXCEPTION_BEGIN
  FOO *foo_ptr = NULL:
EXCEPTION_TRY
  foo_ptr = new FOO(...);
  ...
  bar_ptr = new BAR(foo_ptr,...);
  ...
EXCEPTION_CATCH(FALSE)
  if (bar_ptr == NULL)
    delete foo_ptr;
  else
    delete bar_ptr;
EXCEPTION_END
return bar_ptr;

Important: Several type names (such as bs2_curve and bs3_surface) are actually pointers. Functions that return these types are probably returning pointers to allocated memory.


Exception Handling Cost

Exception handling is relatively cheap, but not free. Consider size and duration of memory use when deciding whether to attempt to catch a potential leak.

// A small allocation, quickly attached
FOO *foo_ptr = new FOO(...);
bar.set_foo(foo_ptr);

User-Level Error Handling Functions

In ACIS-based applications, precede the code that calls ACIS functions with API_BEGIN and succeed the program with API_END. These macros set up and terminate the error system automatically and are transparent to the application.

Error Printing Functions

When an error is generated by an API function, an error code is returned as part of the outcome returned value, or as part of the warning list. The following functions are used to find or print error messages once an error has occurred:

Table. Error Printing Functions
Function Description
find_err_ident Translates the error number to a string containing the mnemonic name associated with the given error number.
find_err_mess Translates the error number to a string containing the message associated with the given error number.
find_err_module Translates the error number to a string containing the name of the module associated with the given error number.
print_warnerr_mess Prints the message associated with the current error number in a simple format for debugging purposes.
get_warnings Obtains the warnings list.
init_warnings Resets the number of warnings to 0.

Error Return Mechanisms

The outcome class contains a pointer to an error_info object. Each API function has the option of returning additional error information in objects derived from error_info. Although no restriction is placed on the information you may attach to such objects, you should be aware that new ENTITYs will be lost during roll back, meaning that pointers to such objects will become stale.

Each error_info object is allocated on the heap, and the outcome cleans up any error_info object it references.

The base class for error_info, error_info_base, provides a standard interface allowing you to write generic code to process information about actual or potential problems found during API function execution. Each error_info_base object is intended to represent one instance of a problem being encountered, and has methods to query the error number or message string of the problem, the severity of the problem, and a list of reasons (themselves error_info_base objects) that provide further information concerning the problem. A virtual type method is provided which allows you to determine the exact subclass to which the error_info_base object can be downcast in order to obtain information specific to the particular problem. The error_info objects returned by outcome may, in addition, have one or more ENTITYs associated with them. The error_info::get_entities_alive method allows you to query the live ENTITYs associated with a problem or failure.

A container class, error_info_list, has been provided to manage collections of error_info objects. (For error_info_base objects, a corresponding error_info_base_list class is also available.) This class allows collections of error_info objects to be passed to user routines as a single object. In addition, the error_info_list provides shared ownership for the error_info objects that it contains. The lifetime of an error_info object is equal to the union of the lifetimes of the containers (error_info_list, outcome, or, for insanity_data objects, insanity_list) to which it has been added. As long as users do not explicitly add reference counts to error_info objects, they need not worry about memory management of these objects. It is recommended that error_info_list objects be placed on the stack wherever possible, so that they do not need to be explicitly deleted by user code.

These error_info objects are used to enhance problem reporting from API functions through the outcome mechanism. When an API encounters a problem during execution, it may report that problem by adding that error_info object to the outcome object that it returns. The outcome object can then be queried for the information. The following member functions of the outcome class provide multiple levels of query, from least detailed to most detailed:

Table. Member Functions of the outcome Class
Method Description
ok FALSE if fatal errors were encountered. A fatal error is one that causes the API function to fail and roll back to its initial state. The error_info object associated with a fatal error has severity SPA_OUTCOME_FATAL.
error_number This provides the error number of the fatal error that caused the ok method of the outcome class to return FALSE. If the ok method returns TRUE then the error number will be zero.
get_error_info This provides a pointer to the error_info object associated with the fatal error that caused the the ok method of the outcome class to return FALSE. The error_number method of this error_info object will return the same value as the error_number method of the outcome class. If ok method of the outcome class returns TRUE, then this method will return a NULL pointer. The error_info object returned by this query gives the highest level of information about the fatal error.
encountered_errors Returns TRUE if a fatal error or one or more failsafe errors were encountered. A failsafe error is an error that a failsafe API function encountered during an atomic operation. Failsafe errors do not cause an API function to roll (unless the careful option is TRUE, in which case they are promoted to fatal errors), but they do indicate that the model resulting from the API function may have problems that will cause downstream operations to fail if they are not corrected. An error_info object with severity SPA_OUTCOME_ERROR is added to the outcome class object for every failsafe error encountered. For more information, refer to the section Failsafe Behavior.
get_error_info_list This returns a list of all error_info objects that have been added to the outcome class object. This list will include the fatal error_info (if any), error_info objects for any failsafe errors that have occurred, and error_info objects for any additional problems that may have been encountered by the API function. Such error_info objects, with severity SPA_OUTCOME_PROBLEM, warn that there may problems with the input data or workflow. However, they do not indicate that the outcome of the API function will be an invalid model, nor are they promoted to fatal errors when the careful option is TRUE. An example of such a problem is passing a wire body to the face stitcher. This will not result in an invalid body, but may indicate that the application is not handling wire bodies properly.

The class error_info is also used as the base class for the insanity_data objects returned from the ACIS Checker. Such error_info objects have severity SPA_OUTCOME_INSANITY, and the error message number returned by the error_number method is the same as that returned by insanity_data::get_insane_id. A helper function, convert_insanity_list_into_error_info_list, is provided to convert an insanity_list returned from the Checker into an error_info_list. With the ability to obtain ENTITY information from an error_info object using the get_entities_alive method, users can write a generic handler routine for displaying both problems encountered during API function execution and problems located by the Checker, in the same format. In addition, the error-message-ID and message-string mechanisms are the same for check errors, sys_errors (fatal or failsafe), and problems.

A code snippet demonstrating how to examine error_info objects is provided in Sample Code.

Error System Process

Step 1

At the start of each API function, a global variable pointer to an error_info_base object is set to NULL.

Step 2

Before sys_error is called, the global pointer is set to contain the relevant error_info_base object.

Step 3

At the end of the API function, before the outcome is returned, the global variable is examined, and if non-empty, an error_info object is added to the outcome. (If the global variable points to a base-class object, an error_info is first constructed from its data.)

Two overloaded versions of the function sys_error set a global pointer to an error_info object. One version is passed an error_info object, and the other creates a standard_error_info object when passed one or two ENTITYs. (The standard_error_info class is derived from error_info, which provides error data that is adequate in many cases, such as in Local Operations and Blending.)

In the Booleans, Local Operations, Remove Faces, and Shelling components, the error_info object returns an ENTITY that further specifies where the operation first fails, when such information is available. A standard_error_info object is adequate for use in these components, and more detailed information could be returned, if necessary, by deriving a new class.

See Also

Personal tools