This message is directed to everyone who wrote HESSI routines and needs a way to abort execution and exit gracefully under certain conditions.
The new error-handling code is included in the latest software upload. The details are a little complicated, so this message is long. Sorry. You can skip right to the WHAT YOU SHOULD DO section if you already know how catch works.
----- CATCH EXPLANATION
CATCH is an IDL procedure that provides a mechanism for handling exceptions and errors within IDL. Calling CATCH establishes an error handler for the current procedure that intercepts errors. When an error occurs, each active procedure, beginning with the offending procedure and proceeding up the call stack to the main program level, is examined for an error handler. If an error handler is found, control resumes at the statement after the call to CATCH. If no error handlers are found, program execution stops, an error message is issued, and control reverts to the interactive mode
The error handling code looks something like this:
if error ne 0 then begin
insert code here to handle the error condition
and either return or set an error flag or something that you will handle, or
call MESSAGE to
bubble up to the next error handler
The errors that will cause execution to jump to the CATCH are normal run-time errors (e.g., trying to use an undefined variable) that would normally cause the code to crash, or exceptions generated by a call to the MESSAGE routine.
So the general idea is:
----- CATCH IN HESSI
After trying different approaches, this is how I thought we could use this in the HESSI objects.
We will put a catch in the GET… methods for all of the primary objects. Primary objects include any object that a user might create directly and make method calls on. That way, no matter which object they start with, there will be a catch established. If an error occurs in a lower-level object, execution will bubble up to each intermediate object’s error handler until it reaches the error handler for the user’s top-level object, at which point it will return a -1 as the value of the GET… function call. For example, if a user called GETDATA for an hsi_image object, and an error occurred in the hsi_aspect_solution object, then execution will bubble up through event handlers as follows:
% HSI_IMAGE::GETDATA: Aborting . Returning -1.
Whereas if the user had created an hsi_aspect_solution object directly and called GETDATA for it, execution would jump to hsi_aspect_solution’s error handler which would return a -1:
HSI_ASPECT_SOLUTION::GETDATA: Aborting. Returning -1.
This means that users who write scripts must check whether the value returned by GET… function calls is -1 before proceeding to use that value in a plot or further calculations. Hopefully users are already doing this.
We will not put any catches
in the SET methods. In the SET methods, even when an error is encountered, we
usually want to continue and set the rest of the parameters in the argument
list. Using CATCH would interrupt that execution. This means there should not
be any calls to MESSAGE without /CONTINUE or /INFO in routines called by the SET
methods. Since there’s no error handler in the chain of object calls above the
SET, execution would halt at the MESSAGE statement.
----- THE DETAILS
I’ve written two snippets, hsi_insert_catch and hsi_message, to implement this for HESSI. I’ve inserted calls to hsi_insert_catch in the key GET, GETDATA, and GETAXIS methods for HESSI objects. I’ve added calls to hsi_message in key places that I know about where error conditions prevent continuing execution (e.g. no FITS file found, no lookup table available, etc).
Note: The hsi_insert_catch and hsi_message snippets are not procedures. They are just lines of code that are included in routines via the @ symbol. This is necessary so that the calls to CATCH and MESSAGE will be directly in the routines that are in the call stack. A procedure is no longer in the call stack once it returns.
Here's what hsi_insert_catch does:
Here’s what hsi_message does:
(If we realize later that
we need to do more, we can hopefully add the code to hsi_message, i.e. in one
place, instead of everyone changing their individual routines.)
Please look at the
documentation headers of hsi_insert_catch and hsi_message (both in $SSW/hessi/idl/util)
for more details on how this works.
I inserted a CATCH into the
following methods of the following routines. :
hsi_lightcurve__define.pro GETDATA, GETAXIS
hsi_spectrogram__define.pro GET, GETDATA,GET_TIMEBIN, GETAXIS
----- WHAT YOU SHOULD DO
If you have places in your code where you can't continue, you should change it. Especially if you're calling STOP now. You should change it to call hsi_message as follows:
1. Set the variable 'msg' to your error message string.
The hsi_message snippet assumes the text of the error message is in a variable called msg, so you must set that first. For example:
msg = ‘No lookup table found. Aborting.’
Remember, only put calls to hsi_message in routines that are called as a result of a GET… function method call. Otherwise there won’t be an error handler in place, and execution will just halt.
In cases where you don't want to abort, you can still call MESSAGE with /CONTINUE or /INFO and the routine will not jump to the catch.
(And actually, you can still call MESSAGE without /CONTINUE or /INFO to cause execution to jump to the error handler. Please see the DETAILS section above for why it might be better to use hsi_message than just calling MESSAGE yourself.)
NOTE: In addition to using
the error handlers that I’ve put in place, you can also establish your own error
handler (insert a CATCH block) in your own routines. When an error is
encountered, this catch block will be used if it is found before any other catch
blocks when looking up the call stack backward. In fact, if you call MESSAGE in
this catch block, it will then filter up to the next catch it finds, which may
be one of the general purpose catches that I added with @hsi_insert_catch, or it
could be another of your own catches.
Two reasons for establishing your own error handlers come to mind:
Here’s an example. Suppose you choose a time when there is no aspect data and try to make an image. Previously it would crash in hsi_sas_limbsel with the error that the variable LIMB is undefined.
With the CATCH, it displays that error message and bubbles up through the GETDATA calls to the HSI_IMAGE::GETDATA call, which returns a -1, as follows:
IDL> o=hsi_image(obs_time='29-mar-02 12:' + ['10','20'])
% Variable is undefined: LIMB.
% Execution halted at: HSI_SAS_LIMBSEL 3 C:\ssw\hessi\idl\aspect\sas\hsi_sas_limbsel.pro
% HSI_SAS 149 C:\ssw\hessi\idl\aspect\sas\hsi_sas.pro
% HSI_ASPECT 281 C:\ssw\hessi\idl\aspect\hsi_aspect.pro
% HSI_ASPECT_SOLUTION::PROCESS 266
…full traceback is given here…
% HSI_IMAGE::GETDATA: Aborting. Returning -1.
IDL> help, data
DATA INT = -1
----- DISABLING CATCH:
If you don't want the catch to be set so you can debug your routines normally, set the debug level to something other than 0. To set or check the debug level, use these IDL commands:
hsi_debug,n where 0 <= n <= 10
When the debug level is non-zero, the catch code is bypassed, and in HSI_MESSAGE, the MESSAGE call has the keyword /CONTINUE. So execution will continue and a crash will halt execution in the code at the error.
You must recreate the object after changing the debug level.
I hope all of this works, but I'm sure we'll have to iterate a few times.