Hi,
In response to Alexander Donev's request for error-handling strategies:
It's important to distinguish errors (caught by the program) from
exceptions (signals). I'm only addressing errors here.
We developed an error handling library with C and Fortran interfaces.
It's object-based. It's described in the README included below,
though the Fortran interface hasn't been extended to all the
features the C interface has.
In simplest practice the way it's used is:
mmerrf_initialize (to initialize the mmerr library)
mmerrf_new (to get a new handler, or else use the default)
mmerrf_file (to specify IO unit for reporting of thrown errors)
mmerrf_throw with an error level (to throw an error)
later,
mmerrf_delete (to delete the handler, unless using the default)
mmerrf_terminate (to return the library to its pristine state)
progrem exit
Though the library has no means of enforcing this, the preferred usage
pattern is (pseudocode):
program prog
...
call foo1( istatus )
if( istatus .NE. OK )then
mmerrf_throw( ihandler, MMERR_FATAL, 'main: foo1 failed' )
exit
end if
end program prog
subroutine foo1( istatus )
integer istatus
...
istatus = OK
call foo2( istatus )
if( istatus .NE. OK )then
mmerrf_throw( ihandler, MMERR_FATAL, 'foo1: foo2 failed' )
istatus = NOT_OK
exit
end if
end subroutine foo1
subroutine foo2( istatus )
integer istatus
...
istatus = OK
if( some_condition_fails )then
mmerrf_throw( ihandler, MMERR_FATAL, 'foo2: problem; can''t go on', istat )
istatus = NOT_OK
exit
end if
end subroutine foo2
Thus, at the end, we effectively have a call stack of err messages
printed out, which is very helpful in debugging, and as often as
possible helpful to users as well. For the above example, the
printout would look something like this:
FATAL foo2: problem; can't go on
FATAL foo1: foo2 failed
FATAL main: foo1 failed
Obviously, in practice, the messages would be more informative. :-)
Most of our interactive programs are written in C or C++; therefore
the main use of the Fortran interface has been for simple batch-level
reporting to an output file (like a log file). Functions useful
for interactive programs (such as delayed reporting or reporting
errors to, say, an interactive window) have not been extended to
Fortran, but they would be, should we develop a need for them.
Improvements could certainly be made to the Fortran interfaces,
particularly in the area of string handling.
-P.
mmerr README:
mmerr - MacroModel error-reporting library; includes functions to
throw and catch errors at various severities, and to specify actions
to be taken when an error of a particular severity is thrown. The
most typical action is the placment of a message in a file, output
stream, or GUI window, and explicit functions are defined which
control this behavior. The mmerr library is object oriented: different
handlers can be allocated, each with its own behavior.
The test drivers tryc (for the C API) and tryf (for the F API) are
supplied. The expected outputs are in tryc.out and tryf.out.
C SYNOPSIS
#include <mmerr.h>
INITIALIZE AND TERMINATE MMERR PACKAGE
Mmerr_level mmerr_initialize( void );
void mmerr_terminate( void );
ALLOCATE AND FREE A HANDLER
Mmerr_level mmerr_new( int *const ihandler );
void mmerr_delete( const int ihandler );
DEFINE A STRING WHICH WILL ALWAYS PRECEDE A HANDLER'S MESSAGES
Mmerr_level mmerr_prefix( const int ihandler, const char *const prefix );
SET AND GET MINIMAL LEVEL FOR REPORTING
Mmerr_level mmerr_level( const int ihandler, const unsigned int level );
Mmerr_level mmerr_get_level( const int ihandler,
unsigned int *const level );
THROW AN ERROR
Mmerr_level mmerr_throw( const int ihandler, const unsigned int level,
const char *const fmt, ... );
DEFINE LOCATION WHERE ERROR MESSAGE WILL BE PLACED
Mmerr_level mmerr_file( const int ihandler, const FILE *const fp );
Mmerr_level mmerr_window( const int ihandler,
Mmerr_window_print const wputs );
SET USER_DEFINED CATCH FUNCTION FOR AN ERROR LEVEL
Mmerr_level mmerr_catch( const int ihandler, const unsigned int level,
Mmerr_func const catch_func, void *const catch_data );
DELAYED ERROR HANDLING
Mmerr_level mmerr_maxlevel( const int ihandler,
unsigned int *const maxlevel );
Mmerr_level mmerr_queue_on( const int ihandler, const int is_active );
Mmerr_level mmerr_queue_get( const int ihandler, char **const str );
Mmerr_level mmerr_errclear( const int ihandler );
RETURN POINTER TO CHAR STRING DESCRIBING AN ERROR LEVEL
char *mmerr_return_code( const unsigned int level );
FORTRAN SYNOPSIS
N.B. Except where otherwise noted, function arguments follow
Fortran naming conventions, except that, for clarity, arguments
shown in capitals have values supplied by the caller, whereas
arguments shown in lower case have values supplied to the caller
by the function. In every case, "istat" is a status variable
which will return either MMERR_OK or MMERR_BUMMER, as described
below.
INCLUDE 'mmerr.inc'
Initialize and terminate mmerr package:
SUBROUTINE MMERRF_INITIALIZE( istat )
SUBROUTINE MMERRF_TERMINATE( )
Allocate and free a handler:
SUBROUTINE MMERRF_NEW( ihandler, istat )
SUBROUTINE MMERRF_DELETE( IHANDLER )
Define a string which will always precede a handler's messages:
SUBROUTINE MMERRF_PREFIX( IHANDLER, PREFIX, istat )
CHARACTER*(MMERR_S_STRLEN) PREFIX
Set and get minimal level for reporting:
SUBROUTINE MMERRF_LEVEL( IHANDLER, ILEVEL )
SUBROUTINE MMERRF_GET_LEVEL( IHANDLER, ilevel, istat )
Throw an error:
SUBROUTINE MMERRF_THROW( IHANDLER, ILEVEL, MESSAGE, istat )
CHARACTER*(MMERR_L_STRLEN)
Define location where error message will be placed:
SUBROUTINE MMERRF_FILE( IHANDLER, IUNIT, istat )
SUBROUTINE MMERRF_WINDOW( !!! not implemented )
Set user_defined catch function for an error level:
SUBROUTINE MMERRF_CATCH( !!! not implemented )
Delayed error handling:
SUBROUTINE MMERRF_MAXLEVEL( IHANDLER, MAXLEVEL, istat )
SUBROUTINE MMERRF_QUEUE_ON( !!! not implemented )
SUBROUTINE MMERRF_QUEUE_GET( !!! not implemented )
SUBROUTINE MMERRF_ERRCLEAR( IHANDLER )
Return pointer to char string describing an error level:
SUBROUTINE MMERRF_RETURN_CODE( IHANDLER, STRING, istat )
CHARACTER*(MMERR_S_STRLEN) STRING
DESCRIPTION
The mmerr library provides an API to an error reporting and handling
facility largely intended for use by writers of code libraries,
but also usable by application programmers.
ihandler, returned by merr_new(), is an instance of an error handler.
User code passes error conditions to such a handler as they arise,
using mmerr_throw(). The other functions either set up actions which the
mmerr library is to perform when an error of a particular severity
occurs or retrieve information about errors after they have
occurred.
mmerr_initialize() initializes the library, mmerr_new() creates a
new error handler, mmerr_delete() deletes an old handler, and
mmerr_terminate(), which is provided primarily to enforce
malloc cleanliness, deallocates all storage previously declared by
the mmerr package; this includes deletion of all extant handlers.
It is legal to call mmerr_initialize() multiple times, but all
calls beyond the first, prior to an mmerr_terminate(), are ignored.
mmerr_throw() is the mechanism by which user or library code declares
to the mmerr package that an error has occurred. For example:
foo = ( struct foo_tag * )malloc( nfoo * sizeof(struct foo_tag );
if( foo == NULL ) {
/* fatal error: malloc() fails: */
( void )mmerr_throw( ihandler, MMERR_FATAL,
"Could not malloc() %d bytes for foo.\n",
nfoo*sizeof(struct foo_tag) );
exit( 1 );
}
ihandler is an error handler either earlier obtained from
mmerr_new() or else is equal to MMERR_DEFAULT_HANDLER (see below).
MMERR_FATAL is one of several predefined error levels available
in mmerr.h. Most user code will probably throw only MMERR_FATAL and
MMERR_WARNING, but a more complete list is: MMERR_OK, MMERR_DEBUG,
MMERR_INFO, MMERR_WARNING, MMERR_FATAL. In addition, MMERR_BUMMER
and MMERR_OFF exist and have special meanings which will be discussed
later; they should not be thrown by user code.
Most of the mmerr library calls set up actions that will be taken
when a throw, such as the one shown above, is received. Unless actions
are properly set up, no action or reporting will take place, unless
MMERR_DEFAULT_HANDLER is used (see below).
mmerr_level() specifies the minimum error level that must be thrown
for any action to take place. For example, if mmerr_level() is
set to MMERR_WARNING, then an mmerr_throw() call specifying a level
of MMERR_INFO will result in no action whatsoever. This allows
the mmerr library to be used as a mechanism for logging debugging
information: if mmerr_throw() is called many places in the code
with a low level, such as MMERR_INFO, then these messages will be
reported only when mmerr_level() is set to this or to the even lower
MMERR_OK value. The default mmerr minimum reporting level is
MMERR_WARNING. If mmerr_level is called with the special value
MMERR_OFF, then error reporting and handling will be turned completely
off for the handler in question.
mmerr_file() allows a file to be specified to which error messages are
to be logged. Specifying a NULL value of the fp variable turns
off file reporting. merr_window() allows a user-defined callback to
be registered. This callback, which is expected to obey the typedef:
typedef int ( *Window_print )( char *string );
is supposed to print its argument to the user's graphical window(s).
It is also expected to return EOF if it knows that it has failed
(for example, if its widget destination has not yet been instantiated,
and if the user wishes to deem this an error). The message passed by
mmerr_throw() is printed to a file or a window if mmerr_file() or
mmerr_window() has previously been used to define such a destination
and if the thrown error exceeds the minimum reporting level specified
in mmerr_level() (MMERR_WARNING by default). When an error is
reported, the text thrown is preceded, on output, by an identifier
reflecting the severity of the error: "WARNING" for a warning, "FATAL"
for a fatal error, etc.
When mmerr_library is initialized, a default error handler is
always set up, and may be referred to by MMERR_DEFAULT_HANDLER.
The default action is to put thrown messages on the stderr output
unit, though, as with any other handler, this action may be altered
by means of mmerr_file().
Most of the other MacroModel libraries accept a single argument
to their initialize() functions: the mmerr handler which the
library is to use. These libraries themselves call mmerr_initialize(),
and proceed to throw errors to the mmerr handler received in their
own initialization calls. In the simplest case, the application
author will initialize all such libraries with MMERR_DEFAULT_HANDLER.
mmerr_prefix() defines a prefix (character string) which will
precede all error messages. For example, if an application is
called "myapp", and if it throws messages itself, in addition
to using several libraries, then, if the application and the
libraries all use the same mmerr handler, calling mmerr_prefix()
with this handler and the string "myapp; " will cause a thrown
WARNING to be preceeded by the string "myapp; WARNING:". On
the other hand, if the application causes each library to use
its own handler, then each handler may be passed its own prefix.
mmerr_catch() allows a user-specified error-catching function
to be registered for any desired error level. If an error of the
specified level is later thrown, this function will be called after
other any other reporting is done, all provided, of course, that the
level of the throw exceeds the minimum reporting level. The
user-supplied callback is expected to conform to the typedef:
typedef void ( *Mmerr_func )( void* arg_list );
The argument to the callback is registered at the same time as the function,
using mmerr_catch()'s catch_data argument. It is the responsibility
of the user code to keep this variable current in the event that the
callback is actually called. The user may call mmerr_catch() with
several functions to be called at different error levels. A
specification of NULL in place of a valid function pointer turns off
this mechanism for the error-level in question.
Several additional mmerr functions support delayed-mode error handling.
mmerr_maxlevel() returns the most severe error that has occurred
since the start of the application or since the most recent call to
mmerr_errclear(). If desired, mmerr_queue_on() may be specified
with a non-zero value in its is_active variable, or later turned
off with is_active set to zero. When it is turned on, any messages
which mmerr would record are appended to internally malloc()ed storage.
A call to mmerr_queue_get() returns a strdup()ed copy of the queue in
the variable str. It is the user's responsibility to free() str when
s/he is done with it; a call to mmerr_errclear() free()s mmerr's copy
of the queue in addtion to resetting the maximum error level recorded
to its initial value of MMERR_OK.
mmerr_return_code() returns an ascii string describing the meaning
of an Mmerr_level value. For example, mmerr_return_code(5) returns
"MMERR_FATAL", given the mmerr.h file as of the time of this
writing.
FORTRAN NOTES
The same error handler may be manipulated and thrown to from
the C and Fortran sides of a mixed-language program.
If a negative value of IUNIT is passed to MMERRF_FILE(), this
has the effect of turning off file-direction of thrown errors;
this is the Fortran equivalent of passing a NULL file-pointer
to the corresponding C-API function.
Only a string, of length MMERR_L_STRING, may be passed as as
message to MMERRF_THROW(); this is in contrast to the C function
mmerr_throw(), which accepts a printf()-style format and a
variable-length list of values to be printed.
Several facilities are not not implemented directly from the
Fortran API. These include specification of output to a GUI
window, of user-specified catch funtions and of delayed-mode
error reporting. However, the writer of a Fortran program
may link in his own C module which calls these mmerr functions
using the handler obtained from the Fortran side.
EXAMPLES
Immediate-mode reporting is recommended for high-level procedures
which are called infrequently and which return a status. Delayed-mode
reporting is recommended for low-level procedures that might be invoked
many times in a row and which do not return a status; a sequence
of such calls might be bracketed by a mmerr_errclear() / mmerr_maxerr()
pair.
For example, let us suppose that the writer of an application using
the the "lib1" library and the lib1 library writer are both making
use of the mmerr facility. The writer of lib1 might supply functions
with prototypes:
Mmerr_level lib1_setup( void ); /* initialize lib1 variables */
Mmerr_level lib1_mmerr( *libhandle ); /* pass lib1's handler to usr */
float lib1_getx( int iatm ); /* get the x coordinate of an atom */
Within lib1, an error handler is allocated, and the library writer has
provided the "lib1_mmerr" mechanism to pass its "ihandle" value
back to the user of the library. By calling mmerr_level(), mmerr_file(),
etc., with this value, the library user may then direct the error-
reporting behavior of lib1. (Alternatively, the writer of lib1 might choose
to hide the identity of its handler to the user, opting instead to
log errors to some generic output file already known to it.)
The user of lib1 might first set up his own error handler using
mmerr_new(&userhandle). Then his code might look like this:
mmerr_level( usrhandle, MMERR_WARNING );
mmerr_file( usrhandle, stdout );
level = lib1_setup();
mmerr_throw( usrhandle, level, "usr: lib1_setup returns %s\n",
mmerr_return_code(level) );
if( level == MMERR_FATAL ) exit( 1 );
mmerr_errclear( libhandle );
mmerr_queue_on( libhandle );
for( iatm=0; iatm<natm; ++iatm ) {
/* assume lib1_getx() throws an error to its mmerr handler
* if it encounters a problem, such as iatm out of range;
* we ignore these errors for now: */
xcoord[ iatm ] = lib1_getx( iatm );
}
/* now see if there were any lib1_getx errors: */
mmerr_maxerr( libhandle, &level );
if( level >= MMERR_WARNING ) {
char *str;
mmerr_queue_get( libhandle, &str );
mmerr_throw( usrhandle, level,
"Error %s encountered during lib1_getx; queue=%s\n",
mmerr_return_code(level), str );
free( str );
mmerr_errclear( libhandle );
/* insert code to work around the error... *.
}
DIAGNOSTICS
A problem arises when an error occurs within the mmerr library itself.
For example, malloc() might return NULL, or a file pointer supplied
by the user might be invalid. Such errors might well prevent
mmerr from logging errors in the manner to which it is accustomed.
For this reason, the mmerr library does not throw errors to itself;
however, most of the mmerr functions do return a status to the calling
code.
The Mmerr_level returned by an mmerr function will be either MMERR_OK,
indicating that no error occurred, or MMERR_BUMMER, indicating that
an unrecoverable error occurred within the mmerr library. Although
the example code shown above does not, for simplicity's sake, check
these error status of mmerr calls, "real" code should do so.
USAGE SUGGESTIONS
The provider of a library using mmerr error handling may, in some
cases, wish to hide the identity of the mmerr handler from the user.
For example, if the library is called "lib1", a simple function
called lib1_errfile() might be provided which takes a single
argument, a file pointer, and sets up mmerr logging to this file.
This saves the user of the library from having to know anything
about mmerr.
On the other hand, the library provider might wish to allow its
mmerr handler to be fully configurable by the end user, as we
have done by making an mmerr handler specifiable in the library
initialization procedue.
Both methods can, in principle, coexist.
--
Peter S. Shenkin Schrodinger, Inc.
VP, Software Development 120 W. 45th St.
646 366 9555 x111 Tel New York, NY 10036
646 366 9550 FAX [log in to unmask]
http://www.schrodinger.com
|