As part of the IBM i 7.3 TR1 and 7.2 TR5 enhancements a new RPG operation code, ON-EXIT, was added. The purpose of this operation code is to give a section of code that is executed whenever a procedure ends, with or without an error. Alas, when an error occurs in the procedure it is not possible to "correct" or prevent the error being returned to the calling program or procedure using the ON-EXIT section. But it does allow for the programmer to add code to, perhaps, flag the error or to perform various clean up tasks before the procedure ends.
The ON-EXIT section must occur at the end of the procedure. It is started by the ON-EXIT operation code, and is ended with the END-PROC operation code that flags the end of the procedure.
dcl-proc TestProcedure ; dcl-s ErrorHappened ind ; // Procedure code on-exit ErrorHappened ; // ON-EXIT section end-proc ;
The ON-EXIT can be used with an indicator, as shown above, or without. If an indicator is given then it is set on when an error happens, and then can be used to condition code within the ON-EXIT section.
All the variables and file defined in the procedure are available within the ON-EXIT section. With the exception of:
- Local SPECIAL files
- Local open access files
- *PSSR subroutines. Use a MONITOR group instead
- Multiple occurrence data structures. Use a data structure array instead
- Tables. Use an array instead
- ON-EXIT is not allowed in a Java native method
- ON-EXIT is not allowed in a cycle-main procedure
- Calls to the APIs CEEDOD, CEEGSI, and CEETSTA
- Operation codes BEGSR, DUMP, EXSR, GOTO, RESET, TAG
So let's have the first example. This program calls a subprocedure to divide two numbers. I am not going to go into details on how to code procedures in free format definition, I will refer you to the post Defining Procedures in RPG all free.
01 ctl-opt dftactgrp(*no) ; 02 dcl-s Result packed(7:3) ; 03 dsply ('Start') ; 04 Result = Division(2:1) ; 05 dsply ('1. Result = ' + %char(Result)) ; 06 Result = Division(0:0) ; 07 dsply ('2. Result = ' + %char(Result)) ; 09 dsply ('End') ; 10 *inlr = *on ; 20 dcl-proc Division ; 21 dcl-pi *n packed(7:3) ; 22 Dividend packed(5) const ; 23 Divisor packed(5) const ; 24 end-pi ; 25 dcl-s Result packed(7:3) ; 26 dcl-s ErrorHappened ind ; 27 Result = Dividend / Divisor ; 28 return Result ; 29 on-exit ErrorHappened ; 30 if (ErrorHappened) ; 31 Dsply ('Divide failed') ; // Any clean up before program ends due to encountered error 32 return -1 ; 33 else ; 34 dsply ('Division successful') ; 35 endif ; 36 end-proc ;
I am going to skip over the code in the main procedure of this program, lines 1 – 10, as I am going to assume you are familiar with the syntax of those lines.
Line 20: DCL-PROC indicates the start of the subprocedure Division.
Lines 21 – 24: This is the procedure interface. Two parameters are passed to the subprocedure, Dividend and Divisor, and one is returned. The definition of a packed variable on the DCL-PI line, line 21, gives the attributes of the returned parameter.
Line 25: This variable will contain the result of the division operation.
Line 26: This indicator will be used to flag if an error occurred.
Line 27: The division occurs.
Line 28: The result is returned to the calling program. If no error was encountered the logic "jumps" straight to the ON-EXIT section, any code that is between this return and the ON-EXIT is not performed.
Line 29: The ON-EXIT flags the start of the ON-EXIT section. As an indicator follows it is on if any error was encountered within the subprocedure, and off if none was.
Lines 30 - 32: If an error happened a message is displayed, using the DSPLY operation code, line 31, and a different value is returned, line 32, to the calling program. It is possible to change the value that is returned to the calling program or procedure in the ON-EXIT section.
Lines 33 – 35: If not error happened then a "success" message is displayed.
Line 36: The subprocedure and ON-EXIT section both end with the END-PROC.
When I call this program this is what I see:
DSPLY Start DSPLY Division successful DSPLY 1. Result = 2.000 The call to DIVISION ended in error (C G D F). C DSPLY Divide failed
The first time the Division subprocedure was called it completed successfully. The second time division operation fails as I cannot divide by zero, therefore, the error message is displayed. I pressed enter, which answers the message with a default of C. As there was an error in the subprocedure the indicator ErrorHappened was turned on, therefore, the "failed" message is displayed. As the program errored is does not complete normally, and the "end" message is not displayed.
To prevent the division operation from failing when one of the values are zero I put it within a MONITOR group. If I replace line 27 with:
27a monitor ; 27b Result = Dividend / Divisor ; 27c on-error ; 27d endmon ;
The MONITOR group prevents the error in the second division, and when I call the program I do not see the error in the second division:
DSPLY Start DSPLY Divide failed DSPLY Start DSPLY Division successful DSPLY 1. Result = 2.000 DSPLY Division successful DSPLY 2. Result = .000 DSPLY End
In this next example I am not using an indicator with the ON-EXIT operation code as I want the code within the ON-EXIT to be executed at all times.
01 dcl-proc TestProcedure ; 02 dcl-pr QCMDEXC extpgm ; 03 *n char(1000) options(*varsize) const ; 04 *n packed(15:5) const ; 05 end-pr ; 06 dcl-s Command varchar(1000) ; // Stuff happens here 07 on-exit ; 08 Command = 'DLTF QTEMP/WORKFILE' ; 09 QCMDEXC(Command:%len(Command)) ; 10 end-proc ;
Line 1: Start of the procedure.
Lines 2 – 5: This is the definition of a procedure to call the QCMDEXC API. A detailed explanation of this can be found in Defining Procedures in RPG all free.
Line 6: Define the variable that will contain the command I wish to execute.
Line 7: The start of the ON-EXIT section. All of the code in this section will be executed whether or not the procedure encountered an error earlier in its code.
Line 8: I want to delete a file from QTEMP.
Line 9: Using QCMDEXC I perform the command.
Line 10: With END-PROC both the ON-EXIT section and the procedure ends.
There is a gotcha when using ON-EXIT. The compiler places the ON-EXIT section into its own subprocedure, with the name "_QRNI_ON_EXIT_" + the name of the subprocedure, for example "_QRNI_ON_EXIT_TestProcedure". When debugging you will have to place a breakpoint within the ON-EXIT section, if you do not and just step though the subprocedure the debugger will step over the ON-EXIT section, as it would with any other subprocedure called from this subprocedure.
You can learn more about the ON-EXIT operation code from the IBM website here.
This article was written for IBM i 7.3 TR1, and will work with 7.2 TR5 too.