Exception/error handling in RPG

It is impossible to test programs to ensure that they will never fail with a CPF message. What you want is a means of handling these errors in a meaningful way.

I have always believed that users should never, ever, see a CPF message issued from an RPG program. It is frustrating

for the help desk, the programmer who has to fix it and especially the user.

Did you ever hear this telephone conversation?

"Help Desk, how can I help you?"

"There is this strange message on the screen."

"Can you tell me what you were doing?"

"Accounts."

"Can you be more specific?"

"One of our customers had a problem with a credit note we had issued. He thought
that the Tax would . . ."

"No. I meant what screen were you on when you got the message?"

"The one in the corner, overlooking the car park."

Or worse still, the user never calls the help desk. They press enter (which takes the
default response of Cancel) and return to the menu. So everything is OK, there is no need
to tell anyone. A CPF message such as Attempt to divide by zero (C G D F) is
meaningless to a user and frightens the life out of most; it might as well say "Missile Launched."

It is impossible to test programs to ensure that they will never fail with a CPF
message; there are too many outside influences that affect a program. What you want is a
means of handling these errors in a more meaningful way. You want something along the
lines of these:

  • The user gets a "friendly screen" that tells them there is a problem -- and it
    does not allow them to return to a menu
  • The Help Desk is notified that there is a problem
  • The program generates a Dump listing
  • A Job Log is printed

All of that can be achieved by writing a CL program, a standard subroutine and a few
minor code changes.

Basically, if a program fails, you want to trap the error and handle the program
failure in a meaningful way. If you are familiar with CL programming, you will be
familiar with the MONMSG command; exception/error handling is the RPG equivalent,
and it is so easy to implement that it always amazes me why every program doesn't
do it.

What is an exception/error?

Your program makes a request (an RPG Operation) to the operating system. The request
fails, and the operating system responds by sending an escape message to the program
message queue. When the escape message arrives a check is made to see if the program is
going to handle the error (MONMSG in CL) and if not, system error handling
kicks in.

In RPG, there are Program Exception/Errors (divide by zero, array index error, etc.)
and File Exception Errors (any file error). As with MONMSG in CL, you can trap errors
at the program level or the command level.

Trapping at the program level

If a program encounters a program error, it checks to see if there is a *PSSR subroutine
coded in the program. If there is, it executes the subroutine. If there isn't, the program
"fails" with an error message. Below is an example of a *PSSR subroutine. The
PSSRDone indicator and the prototype for FatalProgram would usually be included in a
copy member along with other prototypes and standard fields.

D PSSRDone        S               N
D FatalProgram    PR                  ExtPgm('FATALPGM')                                       
                         
       BegSR *PSSR;                                                                                                            
         Dump(A);                                                                                   
                                                                                                    
         If PSSRDone;                                                                               
           *INH1 = *On;                                                                             
           Return;                                                                                  
         Else;                                                                                      
           PSSRDone = *On;                                                                                                             
           FatalProgram();                                                 
         EndIf;                                                                                                
                                                                                                    
       EndSR '*CANCL';

The *PSSR routine shown above performs a DUMP operation and calls
FatalProgram(). FATALPGM should reside in a library that is in the system portion of
the library list and, therefore, in everybody's library list.

Some points to note.

  • The (A) extender means that a DEBUG keyword is not required on the H Spec.
  • The logic with the PSSRDone indicator is in case anything fails in the *PSSR
    subroutine --- which would cause the *PSSR subroutine to be executed, which would
    fail --- which would cause the *PSSR subroutine to be executed, which would fail . . .
    until the number of dump listings causes something to blow.
  • The EndSR for a *PSSR subroutine can contain a return-point instruction.
  • A *PSSR subroutines may be executed like any other subroutine (using ExSR or CASxx).

The following return-point operands can be specified on the EndSR operation for a
*PSSR subroutine, but most of them apply only if you are using the RPG cycle.

  • *DETL. Continue at the beginning of detail output lines
  • *GETIN. Input Record Routine
  • *TOTC. Total Time Calculations
  • *TOTL. Continue at the beginning of total output lines
  • *OFL. Continue at the beginning of overflow lines
  • *DETC. Continue at the beginning of detail calculations.
  • *CANCL. Cancel the Execution of the program
  • Blank. Return control to the RPG default error handler. If the subroutine was
    called by the EXSR operation and factor 2 is blank, control returns to the next
    sequential instruction.

When *PSSR is invoked by an error, it does not return to the statement in error. It
should be used as an exit from the program.

The fatal program

The code below shows a simple example of a fatal error handler. The program prints a
job log, sends a message to the system operator and, if running interactively, displays a
meaningful/helpful screen for the user. (The user has to press enter 1,000 times to get out
of the screen!). You decide what you want to put on the screen format FATALERROR in
the display file FATALDSP -- just keep it friendly and non-threatening. Don't take this as
the finished article. It is only intended to demonstrate the type of thing you can do. What
you do is totally up to you. Make it as complex or as easy as it needs to be.

      DclF  FatalDsp
      Dcl   &Count      *Dec  (5 0)
      Dcl   &Type       *Char (1)

      DspJobLog *Print
      SndMsg ('Help, HElp, HELp, HELP') ToMsgQ(QSysOpr)

      RtvJobA Type (&Type)
      If (&Type = '0') Goto End
Loop: SndRcvF RcdFmt(FATALERROR)
      ChgVar &Count (&Count + 1)
      If  (&Count < 1001) Goto Loop

End:  EndJob *Immed 

The benefit of the above example is that all the user sees is the "friendly" screen
while the help desk is automatically notified of the problem and the programmer has a
job log and a dump listing to work from. That means in about 90% of the incidents,
neither the help desk nor the programmer needs to talk to the user until the problem has
been solved.

Can it get any easier?

Believe it or not, it can be easier. Since a *PSSR routine will be the same in every
program, why not code it in its own member and include it in any program using a
/COPY directive.

The coding overhead of program exception/error handling is now one line of code in
a program.

What about the files?

Just as the program has an error handling subroutine in *PSSR, each file that you define
on an F spec can also have its own error handling subroutine, identified by the INFSR
keyword. Each file can have its own subroutine or a subroutine can be shared between different files.

These subroutines act in exactly the same way as *PSSR, so why not use the *PSSR
subroutine? Below is an example of INFSR being defined on the F specs.

FDisplay   CF   E             WorkStn InfSR(*PSSR)                                                                     

FCustomer  UF   E           K Disk    InfSR(*PSSR)                                                                    

Now, if there is an I/O error with a file, the *PSSR subroutine will be executed.

Well, not quite. The INFSR subroutine will trap all I/O errors except file open errors.
The RPG cycle is automatically opening the files before the code is loaded, therefore it
cannot execute *PSSR if there is a problem during file open (e.g., level check).

To trap file open errors, you need to perform an implicit open of the file by
specifying conditional open for the file (USROPN keyword on F Spec) and use the
OPEN operation to open the file (usually coded in the *INZSR subroutine).

Conclusion

All you have to do is this:

  • Write a standard *PSSR subroutine and place it in a copy member.
  • Write a "Fatal Error" program that is called from the *PSSR subroutine.
  • Include the *PSSR subroutine in programs using a /COPY directive.
  • Include an INFSR(*PSSR) keyword for every file.
  • Implicitly open the files by specifying the USROPN keyword for every file on the F
    spec the using the OPEN operation to open the files.

Just think of all the frustration and irritation that can be saved by making these few
changes.

What about the equivalent of command level MONMSG, where you can trap
individual errors and handle them without terminating the program? Maybe next
month!

---------------------------
About the author: Paul Tuohy is CEO of ComCon, an iSeries consulting company.
He is the author of Re-Engineering RPG Legacy Applications and is one of the quoted
industry experts in the IBM Redbook "Who Knew You Could Do That With RPG IV?"


 

This was first published in July 2004

Dig deeper on iSeries CL programming

0 comments

Oldest 

Forgot Password?

No problem! Submit your e-mail address below. We'll send you an email containing your password.

Your password has been sent to:

SearchEnterpriseLinux

SearchDataCenter

Close