Wednesday, December 7, 2016

ON-EXIT provides code executed at end of procedures

on-exit section procedure and subprocedure

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.

4 comments:

  1. Nice article!

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

    ReplyDelete
    Replies
    1. This is a nice new feature for RPGLE procedures.

      My 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

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

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

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

    ReplyDelete

To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.