In last month's article I discussed exception/error handling at a global level in RPG, where a program shuts down...
in an orderly manner when it receives an unexpected error. But some of the errors may be expected. (Remember: one program's exception can be another program's rule.) It isn't always the case that all program or file errors are unexpected and the program should end. For example, you want your program to trap and handle exceptions for record locking, referential constraints, certain decimal data errors, etc.
So this month I will discuss exception/error handling at the operation level.
Certain operation codes have the equivalent of a command level MONMSG in CL. That means the operation does not fail if it receives an error, and processing continues with the next operation. It is up to the program to determine whether there was an error and what to do about it.
You can trap an exception/error for certain operation codes by either specifying an error indicator in the low position or by using the E (Error) extender on the operation code, as shown in Figure 1. (I include the example with the indicator because you will come across it in legacy code but, of course, the preference is to use the E extender.) You check for an error by testing the indicator or the value of the %ERROR BIF.
C KeyField Chain Customer 90 C If *In90 C KeyField Chain(E) Customer C If %Error Chain(E) KeyField Customer; If %Error;
Figure 1: Examples of trapping exceptions/errors at the command level
The ability to trap errors does not apply to all to all operation codes, but it does apply to all file and "external related" operation codes (CALL, IN, OUT). The table below lists the operation codes eligible for an E extender or error indicator.
Your program has trapped the error, but which error is it? The %STATUS BIF provides a five-digit status code that tells you what the error is. Program Status Codes are in the range 00100 to 00999 and File Status Codes are in the range 01000 to 01999. Status Codes in the range 00000 to 00050 are considered to be Normal (i.e. they are not set by an error condition).
Status codes correspond to RPG Run time messages file in QRNXMSG (e.g., Message RNQ0100 = Status Code 00100). You can view the messages using the command:
DSPMSGD RANGE(*FIRST *LAST) MSGF(QRNXMSG) DETAIL(*BASIC)
The below table lists some of the more commonly used Status Codes. Refer to the chapter on "File and Program Exception/Errors" in the ILE RPG Reference manual for a full list of status codes.
|00100||Value out of range for string operation|
|00102||Divide by zero|
|00112||Invalid Date, Time or Timestamp value.|
|00121||Array index not valid|
|00122||OCCUR outside of range|
|00202||Called program or procedure failed|
|00211||Error calling program or procedure|
|00222||Pointer or parameter error|
|00401||Data area specified on IN/OUT not found|
|00413||Error on IN/OUT operation|
|00414||User not authorized to use data area|
|00415||User not authorized to change data area|
|00907||Decimal data error (digit or sign not valid)|
|01021||Tried to write a record that already exists (file being used has unique keys and key is duplicate, or attempted to write duplicate relative record number to a subfile).|
|01022||Referential constraint error detected on file member.|
|01023||Error in trigger program before file operation performed.|
|01024||Error in trigger program after file operation performed.|
|01211||File not open.|
|01218||Record already locked.|
|01221||Update operation attempted without a prior read.|
|01222||Record cannot be allocated due to referential constraint error|
|01331||Wait time exceeded for READ from WORKSTN file.|
Figure 2 shows an example of checking for another program having a lock on a record that your program is trying to get for update. The E extender traps the error, which is checked for on the next line using the %ERROR BIF. The error is handled if the status is 1218, otherwise your program falls back on the trusty *PSSR routine. Note the use of the file name with the %STATUS BIF to enable you to differentiate between the current statuses of different files.
Chain(E) KeyField Customer; If %Error; If %Status(Customer) = 1218; // Handle the record lock Else; ExSR *PSSR; EndIf; EndIf;
Figure 2: Checking for record already locked.
What about the other operation codes?
V5R1 of OS/400 saw the introduction of a MONITOR group. It allows you to monitor a number of statements for potential errors, as opposed to doing them one at a time. In structure it is quite similar to a SELECT group. The boundary is set by a MONITOR and an ENDMON operation. The MONITOR operation is followed by the lines of code that you want to monitor. The code is followed by a set of ON-ERROR operations that indicate the error conditions that are being monitored along with the appropriate code to handle the error. If there is no error in the code, control branches to the ENDMON operation when the first ON-ERROR operation is reached. If an error is detected in the code, control branches to the ON_ERROR operations which are processed in sequence -- working in the same way as WHEN operations in a SELECT group. When the code for an ON-ERROR segment has been processed, control passes to the ENDMON operation. Monitor groups may be nested.
Figure 3 shows a simple example of a Monitor group. There are several errors that may occur for these few lines of code. There may be an error on the READ operation: The monitor handles a file not open error separately from other file errors by having an ON-ERROR operation for status 1211 and an ON-ERROR operation for all other file errors (*FILE). The next ON-ERROR operation traps potential string errors (status 100) and array index errors (121). The final ON-ERROR operation traps all other errors.
Monitor; Read Customer; If Not %EOF; Line1 = %SUBST(Line(i): %SCAN('***': Line(i)) + 1); EndIf; On-Error 1211; // ... handle file-not-open On-Error *FILE; // ... handle other file errors On-Error 00100 : 00121; // ... handle string error and array-index error On-Error; // ... handle all other errors EndMon;
Figure 3: Using a Monitor group
A Monitor group also applies to code executed in called subroutines. The Monitor group shown in Figure 4 works the same as the one shown in Figure 3. The same cannot be said for subprocedures; if the code in a subprocedure fails, you can trap the error on the call to the subprocedure by checking for status 202.
Monitor; ExSR SubEx; On-Error 1211; // ... handle file-not-open On-Error *FILE; // ... handle other file errors On-Error 00100 : 00121; // ... handle string error and array-index error On-Error; // ... handle all other errors EndMon; BegSR SubEx; Read Customer; If Not %EOF; Line1 = %SUBST(Line(i): %SCAN('***': Line(i)) + 1); EndIf; EndSR;
Figure 4: Using a Monitor group to monitor code in a subroutine
If you need more information about the status of a program or any of the files in the program, you can make use of special data structures. The program status data structure provides information about the program, and file information data structures provide information about the files being used in the program.
A program status data structure is identified by an SDS definition, and there can be only one per program. A file information data structure is associated with a file using an INFDS keyword on the F Spec, and it must be unique for a file. These data structures contain an immense amount of information about the program and files and not just information relating to errors. Again, refer to the chapter on "File and Program Exception/Errors" in the ILE RPG Referencefor a full list of the contents of the data structures.
Figure 5 shows an example of a program status and a file information data structure. Keywords may be used in place of from/to positions for certain information, such as the procedure name. The MSGID fields identify the relevant CPF or MCH error message received by the program. The MIN_RRN field identifies the RRN of the subfile record at the top of the screen when it was input (which is a lot more dependable then using SFLCSRRRN). PROCEDURENAME identifies the name of the program and USERPROFILE identifies the user profile in the job id.
FDisplay CF E WorkStn F InfDS(DisplayInfDS) F InfSR(*PSSR) D DisplayInfDS DS NoOpt D Qualified D MsgId 40 46 D Min_RRN 378 379I 0 D ProgramStatus SDS NoOpt D Qualified D ProcedureName *Proc D MsgId 46 52 D UserProfile 254 263
Figure 5: Program status and file information data structures
Then there is ILE
The ILE environment introduces a couple of considerations for error handling.
The first of these is percolation, where exceptions/errors are percolated to the previous call stack entry until the message is handled or until an AG boundary is met as opposed to the program failing and immediately displaying an error message. In other words, if the program does not handle an error, it is "percolated" back up the call stack.
The second is that you can write your own exception/error handler, which you register in your programs using the CEEHDLR API.
Refer to Chapter 9 of the ILE Concepts manual for further information.
Priority of handlers
This is the priority of the error handlers in RPG IV:
- 'E' Operation extender (or Error Indicators)
- Monitor groups
- File Exception error subroutine
- ILE Condition handler
- Program Exception Error Subroutine (*PSSR)
- RPG default error handler
In the end . . .
I started the previous article by stating that I have always believed that users should never, ever, see a CPF message issued from an RPG program.
I hope that this article and the preceding article have demonstrated how you can ensure your users and your help desk are saved a lot of frustration and irritation.
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?"