IBM Support

Receiving Messages from the Joblog after the Program is Off the Callstack

Troubleshooting


Problem

This document contains information on receiving a message that was sent to a program; however, the program is no longer active in the stack.

Resolving The Problem

The following is information on receiving a message that was sent to a program but now the program is no longer active in the stack. In summary, the only way to receive this type of message is to have the message key of the message.

The following is an example using CL and ILE/C.

******************************************************************************************************
Occasionally there is a need to receive messages from the job log that were sent to a program message queue that is no longer active. For example, PGMA calls PGMB, which sends a diagnostic message to itself and an escape message to PGMA. PGMA can easily receive the escape message that was sent to it, but sometimes it necessary to also receive the diagnostic message. This can be done, but it takes more work than merely receiving the message.

The following points are important in understanding how this works:

1.If the RCVMSG command (or QMHRCVPM API) is coded to receive a message by key from its own program message queue, it will receive the message with that key (if one exists) from the job log, regardless of what invocation the message was sent to or whether that invocation is active or not.
2.The message key is a 4-byte unsigned integer (even though it is documented externally as a 4-character value) that is incremented for each message sent.
This was not true prior to V2R3. Therefore, this technique will not work on earlier releases. In addition, it is not true for nonprogram message queues (QSYSOPR, user message queues, workstation message queues, and so on), but that is not relevant for this technique.

The following steps are required to do this:
1.Send a message (and optionally remove it) to determine the starting message key.
2.Run the function that will send the messages you are interested in.
3.Send another message to determine that ending message key.
4.In a loop, increment the message key between the starting and ending message keys determined in Steps 1 and 3 and receive the message for each key. There can be gaps in the message key (for example, a message may have been sent and then deleted; also, exception handling sometimes assigns message keys internally that cannot be accessed). Therefore, the CPF2410 exception must be handled from the receive message function.
There are other techniques that can be used to access some or all of the messages in the job log. These are summarized here, but are not explained in detail:
1.Write the job log to an output file. This can be done by specifying OUTPUT(*OUTFILE) on the DSPJOBLOG command or by using the QMHCTLJL API to define the job log output file specifications and ending the job or specifying OUTPUT(*APIDFN) on the DSPJOBLOG command. When you do this, all messages in the job log will be written to the output file.
2.Use the QMHLJOBL API to copy messages from the job log into a user space. You can select all of the messages in the job log or a subset, and you can select which fields are returned for each message. There are APIs that can be used to access the contents of the user space. QUSPTRUS can be used to get a pointer to the data in the user space; this can be used with languages that support pointers, such as C or COBOL. QUSRTVUS can be used to retrieve data from the user space; this can be used with languages that do not support pointers, such as CL.
*******************************************************************************
* CL program example
*
*******************************************************************************
Two examples of how to do this in CL are shown here. These will work in both ILE CL and OPM CL.

The first example uses the RCVMSG CL command to receive the messages. This has the advantage of being easier to code and is easier to follow. It has the disadvantage of requiring the program to monitor for and handle the CPF2410 exception, which is likely not acceptable if performance is important.

PGM
DCL &LOWKEY *CHAR 4
DCL &HIKEY *CHAR 4
DCL &MSGKEY *CHAR 4
DCL &MSG *CHAR 256
/*-----------------------------------------------------------------*/
/* DETERMINE STARTING MESSAGE KEY                                  */
/*-----------------------------------------------------------------*/
SNDPGMMSG  MSG(TEST) TOPGMQ(*SAME) KEYVAR(&LOWKEY)
RMVMSG     MSGKEY(&LOWKEY)
/*-----------------------------------------------------------------*/
/* EXECUTE FUNCTION                                                */
/*-----------------------------------------------------------------*/

  ---- Insert whatever command(s) or program call(s) you want ----
  ---- to handle messages for here                            ----

/*-----------------------------------------------------------------*/
/* DETERMINE ENDING MESSAGE KEY                                    */
/*-----------------------------------------------------------------*/
SNDPGMMSG  MSG(TEST) TOPGMQ(*SAME) KEYVAR(&HIKEY)
RMVMSG     MSGKEY(&HIKEY)
/*-----------------------------------------------------------------*/
/* LOOP TO RECEIVE MESSAGES WITH RCVMSG COMMAND                    */
/*-----------------------------------------------------------------*/
CHGVAR %BIN(&MSGKEY 1 4)  (%BIN(&LOWKEY 1 4) + 1)
LOOP:
RCVMSG     PGMQ(*SAME (*)) MSGKEY(&MSGKEY) RMV(*NO) MSG(&MSG)
MONMSG CPF2410 EXEC(DO) /* HANDLE MSGKEY NOT FOUND                 */
  RCVMSG     MSGTYPE(*EXCP) RMV(*YES) /* REMOVE UNWANTED EXCEPTION */
  GOTO       SKIP
ENDDO
     
    ---- Insert code here to do whatever processing is needed  ----
    ---- for the message.  You may need to add additional      ----
    ---- values, such as message ID, message type, etc., to    ----
    ---- the RCVMSG command.                                   ----

SKIP:
CHGVAR %BIN(&MSGKEY 1 4)  (%BIN(&MSGKEY 1 4) + 1)
IF (&MSGKEY *LT &HIKEY) GOTO LOOP
ENDPGM


The next example is similar to the first example. The only difference is that it uses the QMHRCVPM API rather than the RCVMSG CL command. Using the error code structure eliminates the need to handle the CPF2410 exception and the overhead required to send the exception in the case of the message key not being found. This example shows how to extract the message text from the structure returned by the API. If the message is an impromptu message (for example, no message ID), the message is in the Replacement data or impromptu message text area; otherwise, it is in the Message area that follows the Replacement data field. The example checks the message length to determine which of these fields to use for the message.

PGM
DCL &LOWKEY *CHAR 4
DCL &HIKEY *CHAR 4
DCL &MSGKEY *CHAR 4
DCL &MSG *CHAR 256
/*------------------------------------------------------------------*/
/* Some messages have a large amount of message data, in which case */
/* the size of the &MSGINFO variable will not be adequate.  If you  */
/* expect to receive messages with a large amount of message data,  */
/* you will need to increase the size of this variable accordingly. */
/* Be sure to also change the value that is put into &MSGINFOL to   */
/* reflect the size of the variable.                                */
/*------------------------------------------------------------------*/
DCL &MSGINFO *CHAR 1000
DCL &MSGINFOL *CHAR 4
DCL &ERRCODE *CHAR 16
DCL &MSGOFFS *DEC (4 0)
DCL &MSGLEN *DEC (4 0)
/*-----------------------------------------------------------------*/
/* DETERMINE STARTING MESSAGE KEY                                  */
/*-----------------------------------------------------------------*/
SNDPGMMSG  MSG(TEST) TOPGMQ(*SAME) KEYVAR(&LOWKEY)
RMVMSG     MSGKEY(&LOWKEY)
/*-----------------------------------------------------------------*/
/* EXECUTE FUNCTION                                                */
/*-----------------------------------------------------------------*/

  ---- Insert whatever command(s) or program call(s) you want ----
  ---- to handle messages for here                            ----

/*-----------------------------------------------------------------*/
/* DETERMINE ENDING MESSAGE KEY                                    */
/*-----------------------------------------------------------------*/
SNDPGMMSG  MSG(TEST) TOPGMQ(*SAME) KEYVAR(&HIKEY)
RMVMSG     MSGKEY(&HIKEY)
/*-----------------------------------------------------------------*/
/* LOOP TO RECEIVE MESSAGES WITH QMHRCVPM API                      */
/*-----------------------------------------------------------------*/
CHGVAR %BIN(&MSGKEY 1 4)  (%BIN(&LOWKEY 1 4) + 1)
CHGVAR %BIN(&MSGINFOL 1 4) 1000
CHGVAR %BIN(&ERRCODE 1 4) 16
LOOP2:
CALL QMHRCVPM (&MSGINFO &MSGINFOL RCVM0200 '*          ' +
               X'00000000' '*ANY      ' &MSGKEY X'00000000' +
               '*SAME     ' &ERRCODE)
IF ((%BIN(&MSGINFO 5 4) *GT 0) *AND (%BIN(&ERRCODE 5 4) *EQ 0)) +
  DO /* IF A MESSAGE WAS RECEIVED */
    IF (%BIN(&MSGINFO 161 4) *EQ 0) +
      DO /* IMPROMPTU MESSAGE */
        CHGVAR &MSGLEN %BIN(&MSGINFO 153 4)
        CHGVAR &MSGOFFS 177
      ENDDO
    ELSE +
      DO /* STORED MESSAGE */
        CHGVAR &MSGLEN %BIN(&MSGINFO 161 4)
        CHGVAR &MSGOFFS (177 + %BIN(&MSGINFO 153 4))
      ENDDO
    CHGVAR &MSG %SST(&MSGINFO &MSGOFFS &MSGLEN)

    ---- Insert code here to do whatever processing is needed  ----
    ---- for the message.  You can extract additional          ----
    ---- values, such as message ID, message type, etc.,       ----
    ---- from the message information structure if necessary.  ----

  ENDDO
CHGVAR %BIN(&MSGKEY 1 4)  (%BIN(&MSGKEY 1 4) + 1)
IF (&MSGKEY *LT &HIKEY) GOTO LOOP2
ENDPGM


 *******************************************************************************
 * ILE/C program example                                                      
*
 *******************************************************************************

The following sample ILE/C program accepts one or more strings as parameters and runs them as CL commands. All messages sent by the command(s) are received and echoed to the display using printf(). A separate function, GetMsgKey(), is used to determine the current message key. The message key variables used in this program are declared as ints. The prototype definitions for the message handler APIs require that the message key be passed as a char * so a cast is used on the API invocations. A union could also be used to define a numeric and character view of the message key.

#include <qmhrcvpm.h>
#include <qmhrmvpm.h>
#include <qmhsndpm.h>
#include <qusec.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/*-----------------------------------------------------------------*/
/* GetMsgKey() function                                            */
/*                                                                 */
/* This function sends a message to itself to determine the        */
/* the current message key and then deletes the message.  The      */
/* message key is returned to the caller of the function.          */
/*-----------------------------------------------------------------*/
int GetMsgKey(void)
  {
    int MsgKey;
    Qus_EC_t  ErrorCode;
    ErrorCode.Bytes_Provided = 0;
    QMHSNDPM("       ",             /* Message ID                  */
             "                    ", /* Message file name          */
             "TEST",                /* Message text                */
             4,                     /* Length of message text      */
             "*INFO     ",          /* Message type                */
             "*          ",         /* Call stack entry            */
             0,                     /* Call stack counter          */
             (char *)(&MsgKey),     /* Message key - must be cast  */
                                    /* to char *                   */
             &ErrorCode);           /* Error code                  */
      QMHRMVPM("*          ",       /* Call stack entry            */
               0,                   /* Call stack counter          */
               (char *)(&MsgKey),   /* Message key - must be cast  */
                                    /* to char *                   */
               "*BYKEY    ",        /* Messages to remove option   */
               &ErrorCode);         /* Error code structure        */
    return MsgKey;
  }
void main(int argc, char *argv[])
  {
    int LowKey, HighKey, MsgKey;
    int ai;
    Qus_EC_t ErrorCode;
    /*-------------------------------------------------------------*/
    /* The following typdef declares a structure type that contains*/
    /* both the fixed portion of the structure returned by the     */
    /* QMHRCVPM API (defined in qmhrcvpm.h) and the variable area  */
    /* that follows the fixed part of the structure.  There are a  */
    /* a few predefined messages that have more message data than  */
    /* is defined here - if you expect to receive such messages    */
    /* the size of the variable area must be increased accordingly.*/
    /*-------------------------------------------------------------*/
    typedef _Packed struct
      {
        Qmh_Rcvpm_RCVM0200_t RCVM0200;
        char VariableArea[1000];
      } RcvMsgStruct_t;
    RcvMsgStruct_t RcvpmSt;
    int MsgLen;
    char *MsgTextIn;
    char MsgText[256];
    /*-------------------------------------------------------------*/
    /* Get starting message key                                    */
    /*-------------------------------------------------------------*/
    LowKey = GetMsgKey();
    /*-------------------------------------------------------------*/
    /* Perform function(s) that you want to handle messages for.   */
    /* The following code accepts one or more strings passed in    */
    /* as parameters on the call and invokes the system() fucntion */
    /* to execute them as CL commands.  This can be replaced with  */
    /* whatever function you want to handle the messages for.      */
    /*-------------------------------------------------------------*/
    for (ai = 1; ai < argc; ai++) system(argv[ai]);
    /*-------------------------------------------------------------*/
    /* Get ending message key                                      */
    /*-------------------------------------------------------------*/
    HighKey = GetMsgKey();
    /*-------------------------------------------------------------*/
    /* Loop to receive messages                                    */
    /*-------------------------------------------------------------*/
    ErrorCode.Bytes_Provided = 8;
    for (MsgKey = LowKey + 1; MsgKey < HighKey; MsgKey++)
      {
        QMHRCVPM(&RcvpmSt,          /* Message information         */
                 sizeof(RcvpmSt),   /* Length of msg information   */
                 "RCVM0200",        /* Format name                 */
                 "*          ",     /* Call stack entry            */
                 0,                 /* Call stack counter          */
                 "*ANY      ",      /* Message type                */
                 (char *)(&MsgKey), /* Message key cast to char *  */
                 0,                 /* Wait time                   */
                 "*SAME     ",      /* Message action              */
                 &ErrorCode);       /* Error code structure        */
         if ((ErrorCode.Bytes_Available == 0) &&
             (RcvpmSt.RCVM0200.Bytes_Available > 0))
           /*------------------------------------------------------*/
           /* The following section of code extracts the text of   */
           /* the mesasge received and echoes it back to the       */
           /* display using the printf() function.  This can be    */
           /* replaced with whatever processing is needed for the  */
           /* messages.                                            */
           /*------------------------------------------------------*/
           {
             if (RcvpmSt.RCVM0200.Length_Message_Returned > 0)
               {
                 MsgLen = RcvpmSt.RCVM0200.Length_Message_Returned;
                 MsgTextIn = &RcvpmSt.VariableArea[0] +
                       RcvpmSt.RCVM0200.Length_Data_Returned;
               }
             else
               {
                 MsgLen = RcvpmSt.RCVM0200.Length_Data_Returned;
                 MsgTextIn = &RcvpmSt.VariableArea[0];
               }
             memcpy(MsgText,MsgTextIn,MsgLen);
             MsgText[MsgLen]='\0';
             printf("%s\n",MsgText);
           }
      }
  }                                  

[{"Product":{"code":"SWG60","label":"IBM i"},"Business Unit":{"code":"BU058","label":"IBM Infrastructure w\/TPS"},"Component":"Programming (Languages- compilers- tools)","Platform":[{"code":"PF012","label":"IBM i"}],"Version":"Version Independent","Edition":"","Line of Business":{"code":"LOB57","label":"Power"}}]

Historical Number

365516823

Document Information

Modified date:
18 December 2019

UID

nas8N1015739