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:

 

catch, error
if error ne 0 then begin
   catch, /cancel
   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
endif

 

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:

  1. Establish error handlers in key places in the object methods.
  2. Call MESSAGE in routines that encounter conditions in which they can’t continue (e.g. a key piece of data is unavailable).
  3. When an error occurs, execution will jump to the nearest error handler found looking backward up the call stack.
  4. The error handler usually prints a message saying what happened, and exits gracefully.

 

----- 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_ASPECT_SOLUTION::GETDATA: Aborting

HSI_CALIB_EVENTLIST::GETDATA: Aborting

% 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:

  1. Establishes an error handler by calling CATCH.
  2. When an error occurs somewhere downstream, execution jumps to the line after the call to CATCH.
  3. If the error was caused by a normal error, print the error message and the traceback.
  4. If the error was caused by a MESSAGE call, print the error text supplied in the call to MESSAGE.
  5. If we are in a nested level of GETDATA calls, generate another exception (by calling MESSAGE with text like ‘HSI_EVENTLIST::GETDATA: Aborting’) which will cause execution to bubble up to the next error handler in the call stack.
  6. Step 5 repeats until we’re at the highest level GETDATA call.  At that point, MESSAGE is called again with an abort message, but with /CONTINUE so no exception is generated, and the GETDATA function returns a -1.

 

Here’s what hsi_message does:

  1. Calls MESSAGE.
  2. Uses /CONTINUE on MESSAGE call if in debug mode (more on that later).

(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_aspect_solution__define.pro    GETDATA
hsi_binned_eventlist__define.pro   GETDATA
hsi_calib_eventlist__define.pro      GETDATA
hsi_eventlist__define.pro                GETDATA

hsi_image__define.pro                     GETDATA
hsi_lightcurve__define.pro              GETDATA, GETAXIS
hsi_packet_file__define.pro            GETDATA
hsi_spectrogram__define.pro          GET, GETDATA,GET_TIMEBIN, GETAXIS

hsi_spectrum__define.pro                              GETDATA

 

 

----- 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.

2. @hsi_message

 

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.’

@hsi_message

 

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:

  1. You can make your code more robust - sometimes there are too many unpredictable errors that can occur (like when you’re dealing with a user’s input) and the catch will trap any of these errors.
  2. You may want to trap certain errors so that you can handle them yourself.

 

 

----- EXAMPLE

 

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'])

IDL> data=o->getdata()

% 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_ASPECT_SOLUTION::GETDATA: Aborting

HSI_CALIB_EVENTLIST::GETDATA: Aborting

% 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

print,hsi_get_debug()

 

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.

Kim