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.
Nice article!
ReplyDeleteBut I think the focus for ON-EXIT should not be on error detection. As you mentioned, you can use MONITOR and ON-ERROR for that.
The indicator for ON-EXIT indicates whether the procedure was _cancelled_, which could have occurred for other reasons than an error (such as the subsystem ending).
This is a nice new feature for RPGLE procedures.
DeleteMy first thought of how I will use it is to include exit validation logic in the ON-EXIT section, so I can be sure that all business rules are respected in the procedure and that the procedure ran as expected.
In some cases I might need to throw and exception if the rules where not met, which raises the question:
Can I throw an exception in a ON-EXIT?
JG
It would be nice to have it in a CL Program. If I change the library list in the program, upon exit I would like to get back to the same library list that was before calling the program. I have a program that saves the libraray list, and a program that restores it. I want to make sure to run the restore in any case, even if the program got terminated.
DeleteFor the scenario i wrote, it would be even better if the CHGLIBL and ADDLIBLE would have an option to indicate tat it is just for this call level, similar to OVRSCOPE in OVRDBF.
You could call your CL program from an RPG procedure with an ON-EXIT, and then in the ON-EXIT section, call another CL procedure to restore the library list.
ReplyDeleteWe like keeping our programs CL-less :) Less "CL and more SQL" is my slogan.
Deletecan this be used in a programs main procedure? or does it have to be in a sub-procedure? I want to make sure if my program is ended *IMMED that I capture who and maybe do some clean up like resetting DTAARA flags etc
ReplyDeleteIt can be in the main procedure, it can be in a subprocedure, it can be in an external procedure bound to the program, it can be in all three at the same time.
DeleteIt can do what you want it to do.
maybe I am not clear.
DeleteWhen I put ON-EXIT; in my SQLRPGLE program I get this error
"ON-EXIT IS NOT ALLOWED IN A CYCLE-MAIN PROCEDURE"
This is not a Service program but ILE program with a main procedure not defined as DCL-PR/PROC?
Is the program using a "main" procedure?
DeleteIf it is not then you cannot use the ON-EXIT.
You can use ON-EXIT in a linear-main procedure. (Using the MAIN keyword in the H spec.)
ReplyDeleteIf you don't have either the MAIN keyword or the NOMAIN keyword, then the main procedure is a "cycle-main procedure". In that case, your main procedure could call a subprocedure to do its work, and that subprocedure could have an ON-EXIT.
Very useful and elegant, especially to monitor errors. Thanks Simon.
ReplyDeleteI love this on-exit. I use it on all my new programs to close cursors, dspf, prtf or pfs
ReplyDeletei like that on-exit a lot, good share Simon.
ReplyDelete