Skip to content

Error Handling Macros

Ben Auer edited this page Aug 2, 2022 · 52 revisions

Introduction

MAPL provides some error handling macros (defined via the preprocessor) that can make your Fortran code cleaner. The these are used in cases when your routine (a subroutine or function), either a routine you are creating or a routine you are calling provides an optional return code like so:

subroutine foo(...,rc)
integer, intent(out), optional :: rc

! if something goes wrong
if (present(rc)) then
   rc = bad_return_value
   return
end if

! and at the end
if (present(rc)) then
   rc = good_return_value
end if

and when calling foo you would want to do something like this:

integer :: status

call foo(...,rc=status)
if (status /= good_return_value) then
! do something here, maybe report where I am to standard out
! and return rather than continue execution of this procedure
end

Note that most MAPL and all ESMF routines have optional return codes. In addition, other libraries also provide these such as MPI to name one. If you had to write code that in every place that would get tedious fast. As a service to uses MAPL defines a slew of macros to automate this boilerplate code and provide different behaviours and more informative error trapping.

Advice

Error handling is most useful if it is used everywhere in the calling chain. If something throws a fault, but then execution continues because some procedure in the calling tree did not check the return code from one of the procedures it calls, well this is bad. If something threw an error, there was probably a good reason but if not trapped it will probably just lead to some other problem later and will result in more confusion during debugging.

Provided Error Macros

Required includes

To use the MAPL error handling macros your module/subroutine/function should obviously needs to use the MAPL library and include a specific header file MAPL_Generic.h As an example:

#include "MAPL_Generic.h"
module foo
use MAPL


end module

_VERIFY

_VERIFY handles the case when you have called a subroutine/function from some program unit (another subroutine or function) and included the optional return code like so:

integer :: status

call foo(...,rc=status)

Now you want to check that the status is a good or bad value, otherwise what's the point of adding the rc=status argument. Generally if the value is "bad" you would want to return rather than continue execution of the program unit. It would be even better if you got message with the status and maybe where you failed. That's what _VERIFY is for. You can just add this after the call to foo:

call foo(...,rc=status)
_VERIFY(status)

the _VERIFY macro will check that status is the "good" value, if it is execution of the program unit will continue. If the status is anything but the good value the _VERIFY will trigger a premature return from the program unit. Before it exits it will print the status, filename, and line number to standard out. Finally if the program unit itself has an optional rc argument, rc will be set to status.

_RC

We can take the _VERIFY a step further. This is valid Fortran:

call foo(...,rc=status); _VERIFY(status)

note that everything is nicely on one line, but wouldn't it be nice if there was a macro so you didn't have to type the rc=status; there is, it is _RC. The code above can be replaced with:

call foo(...,_RC)

for even more compact and readable code. We suggest using this.

_ASSERT

Sometimes you may have some logical condition you want to check in your procedure, if met just continue but if not met you would want to return, provide a message, and set the return status to the failure value if it was passed. The _ASSERT macro provides this ability. It can be used like so:

logical :: my_conditional

! code does stuff
! code sets my_conditional
_ASSERT(my_conditional,"Provide informative message here")

_FAIL

_RETURN

_SUCCESS

_STAT

_IOSTAT

Error Handling at the "program" scope

What happens though if the scope you are in is the "main" program rather than a subroutine or function. When you check the return code from a call there is nowhere to return to; rather you want to stop execution of your program at this point. MAPL does have something to handle this.

Legacy Macros

In GEOS code outside of MAPL you will probably see macros that start, or start and end with an underscore (VERIFY_, ASSERT_, __RC__ etc) and see a variable named IAM defined. These are legacy macros. They generally provide similar functionality to what has been described above with one crucial difference.

NEW CODE SHOULD NOT USE THESE AND WE HIGHLY ENCOURAGE THAT IF YOU HAVE EXISTING CODE YOU MAINTAIN TO UPDATE TO THE NEW ONES AS THE OLD ONES ARE ERROR PRONE AND LESS INFORMATIVE.

For historical purposes they will be explained here.

Before the macros that begin with an underscore were created MAPL had existing error handling macros that ended in an underscore. The behaviour of these macros did not report the line number and filename. Instead they reported the line number and whatever a local character variable named IAM was set to. The idea was that IAM would be the name of the program unit you are in such as the subroutine name. However, this can not be obtained from the preprocessor so the user would need to explicitly define this via the "IAM" variable. There is nothing that forces the user to set IAM to the name of the subroutine or function. Indeed many times error have occurred because they copied and pasted from another subroutine or if the subroutine has a generic name like "run" that's of little good. We have depreciated these in MAPL but still support them for legacy code. However, we do not endorse the use of these in any new code.

Clone this wiki locally