Message queues tend to become cluttered. Often, you find they have so many messages that it's difficult to locate the messages that need your attention. Many of the messages are obsolete because the jobs that issued them are no longer active. If one of these obsolete messages requires a response (message type inquiry), another problem arises. An operator may spend time discerning an appropriate response when a response isn't necessary.
Suppose a batch job runs into a problem that causes an inquiry message to be sent to the QSYSOPR message queue. A programmer happens to be monitoring this job. He evaluates the job and decides to cancel it with the End Job (ENDJOB) command. The inquiry message remains in the QSYSOPR message queue, unanswered. An operator monitoring QSYSOPR encounters the message and attempts to respond to it. He may even call a programmer or his manager for assistance in determining an appropriate response. Since the message is obsolete, a response is meaningless. The system has tricked the operator and wasted at least one person's time.
This problem has always bothered me, especially because the S/34 and S/36 cleared inquiry messages when their associated jobs were cancelled. Of course, you can display the message queue and remove selected or unanswered messages, or even all messages. However, selectively removing messages is time-consuming; and removing all messages, although quick, may be overkill. The best solution would require IBM to change the operating system. Until that happens, the Clear Obsolete Messages (CLROBSMSG) command presented here can solve the problem.
How to Use CLROBSMSG
The CLROBSMSG utility clears a message queue of any inquiry messages issued by a job that is no longer active. If an operator runs CLROBSMSG for a message queue before he displays the message queue, he can be sure he won't waste time responding to obsolete inquiry messages. You'll probably find the command most useful with the QSYSOPR message queue, but there may be other candidates-for example, message queues assigned to printers.
To use the command, just key CLROBSMSG and press the F4 (Prompt) key. A panel is displayed with the only parameter required by CLROBSMSG-the qualified message queue (MSGQ) name.
The message queue and library must have valid object names and they must exist on your system. In addition, the command must be able to allocate the message queue. Suppose your system operator has the QSYSOPR message queue allocated at the console. If he submits the command to batch, the job will abort. However, he can run it interactively at any time.
How to Install
To install the utility, create the command source member (CLROBSMSG) listed in 1, the CL program (OBS001CL) in 2 and the RPG program OBS001RG in 3. Compile the source members according to the instructions at the beginning of each figure.
To install the utility, create the command source member (CLROBSMSG) listed in Figure 1, the CL program (OBS001CL) in Figure 2 and the RPG program OBS001RG in Figure 3. Compile the source members according to the instructions at the beginning of each figure.
An Overview of How It Works
If you like to read source code to interpret how an application works, you may be able to skip this section and reference the source code presented in Figures 1-3. For some assistance in deciphering how the utility works, read on.
CLROBSMSG first performs an implicit call to validity-checking program OBS001CL, reproduced in 2. This call happens automatically because OBS001CL is specified in the validity-checking program (VLDCKR) parameter of the Create Command (CRTCMD) command when the CLROBSMSG command is created. For more information on validity-checking programs (VCPs), see the accompanying sidebar.
CLROBSMSG first performs an implicit call to validity-checking program OBS001CL, reproduced in Figure 2. This call happens automatically because OBS001CL is specified in the validity-checking program (VLDCKR) parameter of the Create Command (CRTCMD) command when the CLROBSMSG command is created. For more information on validity-checking programs (VCPs), see the accompanying sidebar.
OBS001CL validates the message queue and its library with the Check Object (CHKOBJ) command. If the CHKOBJ command doesn't execute successfully, the program-level Monitor Message (MONMSG) command causes OBS001CL to go to the ABORT label. From here, the error message is sent using the Send Program Message (SNDPGMMSG) command.
OBS001CL determines if the message queue is allocated by executing the Allocate Object (ALCOBJ) command. If message CPF1002 (Cannot allocate objects) is received, the program-level MONMSG command causes a branch to the ABORT label where the message is forwarded to the user. A successful execution of the ALCOBJ command locks the message queue, so the Deallocate Object (DLCOBJ) command is used to remove the lock.
Once the validation program executes successfully, command-processing program OBS001RG (3) executes. It would be easier to write the command- processing program (CPP) as a CL program, using common message commands. However, I chose to use an RPG program and the message application program interfaces (APIs) instead, because this design allows the command to execute much faster. In fact, executing the command interactively without the APIs would be impractical. (A CL version, which I ran against a message queue with 1,000 messages, took about two minutes. The API version took less than 15 seconds.)
Once the validation program executes successfully, command-processing program OBS001RG (Figure 3) executes. It would be easier to write the command- processing program (CPP) as a CL program, using common message commands. However, I chose to use an RPG program and the message application program interfaces (APIs) instead, because this design allows the command to execute much faster. In fact, executing the command interactively without the APIs would be impractical. (A CL version, which I ran against a message queue with 1,000 messages, took about two minutes. The API version took less than 15 seconds.)
The CPP reads each message in a message queue, starting from the top, and determines if the message type is inquiry. If it is, the program determines the status of the message. Obsolete messages are deleted after the next message is read. The basic logic is pretty simple:
o Read message. o Conditionally remove previous message. o Check message type and status.
Let's take a closer look.
Implementation Details: First Message
OBS001RG uses special logic for the first message in the message queue. The details of how a message is processed are the same for the first message and all subsequent messages, but the first message requires a different sequence.
The first message is read by initializing the message key (MSGKEY) with the special value *TOP and using the special value *NEXT for the message type (MTYPE). The message key and type are used as input parameters in the Receive Nonprogram Message (QMHRCVM) API which is called in subroutine RCVMSG. The QMHRCVM API returns the key of the current message through subfield CURKEY. CURKEY is moved to MSGKEY so the next message can be retrieved. (The current key provides a link to the next message.)
Next, subroutine CHKMSG executes to determine if the message type is inquiry. If it is, the program saves the message key into the INQKEY field and the CHKMSG subroutine executes the RTVJOB subroutine to determine whether the job is still active.
Subroutine RTVJOB calls the Retrieve Job Information (QUSRJOBI) API to obtain the message sender's job information and returns the job status in the JOBSTS subfield of the JOBBUF output parameter. If the job is found (indicator 50 off) and the job status is equal to *ACTIVE, blanks are moved to the INQKEY field. INQKEY is used later to determine whether or not to remove the message.
Implementation Details: Subsequent Messages
The program enters a read loop where all messages except the first are processed. Each read retrieves the next message by using the message key (MSGKEY) field of the previous message and specifying *NEXT in the message type (MTYPE) field.
If the previous message is an inquiry message issued by a job that is no longer active, the previous message is removed. (The next message must be read before deleting the previous message because the read operation uses the message key of the current message to retrieve the next message.)
Immediately after the next message is read, the INQKEY field is checked. If the field is not blank, the previous message was an obsolete inquiry message; the message is removed with the Remove Nonprogram Message (QMHRMVM) API in the RMV- MSG subroutine.
The read loop is iterated until no more messages are found.
If old inquiry messages cause you or your operators grief, CLROBSMSG may be the key to a more hassle-free day.
Richard Shaler is a senior technical editor for Midrange Computing.
References CL Programmer's Guide (SC41-8077, CD-ROM QBKA7102). System Programmer's Interface Reference (SC41-8223, CD-ROM QBKA8402).
Validity-checking programs (VCPs) are automatically executed before the command-processing program (CPP). Any errors encountered in the validity program are sent to the user and prevent the CPP from starting. The VCP accomplishes this by sending two special program messages with the Send Program Message (SNDPGMMSG) command as follows:
o The error information is sent as a diagnostic message through a special message, CPD0006, found in the QCPFMSG message file in library QSYS. The error message is sent through the message data (MSGDTA) parameter of the SNDPGMMSG command. You must prefix the message text with a string of four zeros (0000) followed by a blank.
o The command is aborted by sending a special message, CPF0002, as an escape message.
You can see this process in VCP program OBS001CL (2) at the ABORT label.
You can see this process in VCP program OBS001CL (Figure 2) at the ABORT label.
Programmers often ignore VCPs. I believe it's mostly because when CPPs are written in CL (a common occurrence), it's so easy to combine the validity- checking code with the processing code and not have another source member to manage. However, VCPs are worth considering for several reasons.
1. VCPs provide a better user interface than CPPs. If the user prompts the command, a VCP presents error messages to the command-prompt screen. This leaves the screen and its offending parameter displayed for the user to modify. When validity code is placed in the CPP, the command-prompt screen disappears.
2. When you submit a command to batch using the Submit Job (SBMJOB) command, the VCP executes before the batch job is placed on the job queue. This eliminates an aborted batch job and the need to consult a job log. When the job becomes active, the VCP executes a second time. If the command is waiting on the job queue or the job queue is held, and conditions change before the job starts, the system checks the conditions again through the VCP.
3. The VCP and the CPP can be written in different languages. For example, it is often true that the CPP is not written in CL, and you need to use CL commands to perform your validity checking. The VCP can be written in CL to provide an easier interface to command execution than an RPG or other non-CL program.
4. VCPs modularize your application design.
All of the benefits listed above help make the CLROBSMSG utility better- designed and easier to use than if I had not used a VCP.
The only argument against VCPs (that I can think of) is that the VCP requires you to pass and declare the parameters from the command in two places: the VCP and the CPP.
Think about using a VCP the next time you create a command. For more information on VCPs, see "Writing AS/400 Commands," MC, March 1993, or the CL Programmer's Guide.
Message Queue Housekeeper
Figure 1 CLROBSMSG Command Specifications
/*==================================================================*/ /* To compile: */ /* */ /* CRTCMD CMD(XXX/CLROBSMSG) PGM(XXX/OBS001RG) + */ /* SRCFILE(XXX/QCMDSRC) VLDCKR(XXX/OBS001CL) */ /* */ /*==================================================================*/ CLROBSMSG: CMD PROMPT('Clear Obsolete Messages') PARM KWD(MSGQ) TYPE(QUAL1) MIN(1) PROMPT('Message + queue') QUAL1: QUAL TYPE(*NAME) LEN(10) QUAL TYPE(*NAME) LEN(10) DFT(*LIBL) + SPCVAL((*LIBL)) PROMPT('Library')
Message Queue Housekeeper
Figure 2 Validity Checking Program OBS001CL/*==================================================================*/ /* To compile: */ /* */ /* CRTCLPGM PGM(XXX/OBS001CL) SRCFILE(XXX/QCLSRC) */ /* */ /*==================================================================*/ OBS001CL: + PGM PARM(&QUALMSGQ) DCL VAR(&MSGQ) TYPE(*CHAR) LEN(10) DCL VAR(&MSGQLIB) TYPE(*CHAR) LEN(10) DCL VAR(&QUALMSGQ) TYPE(*CHAR) LEN(20) DCL VAR(&MSGTXT) TYPE(*CHAR) LEN(132) DCL VAR(&MSGTEXT) TYPE(*CHAR) LEN(137) MONMSG MSGID(CPF0000) EXEC(GOTO CMDLBL(ABORT)) CHGVAR VAR(&MSGQ) VALUE(%SST(&QUALMSGQ 1 10)) CHGVAR VAR(&MSGQLIB) VALUE(%SST(&QUALMSGQ 11 10)) CHKOBJ OBJ(&MSGQLIB/&MSGQ) OBJTYPE(*MSGQ) ALCOBJ OBJ((&MSGQLIB/&MSGQ *MSGQ *EXCL)) WAIT(0) DLCOBJ OBJ((&MSGQLIB/&MSGQ *MSGQ *EXCL)) GOTO CMDLBL(ENDPGM) ABORT: + RCVMSG MSGTYPE(*EXCP) MSG(&MSGTXT) CHGVAR VAR(&MSGTEXT) VALUE('0000' *BCAT &MSGTXT) SNDPGMMSG MSGID(CPD0006) MSGF(QCPFMSG) MSGDTA(&MSGTEXT) + MSGTYPE(*DIAG) SNDPGMMSG MSGID(CPF0002) MSGF(QCPFMSG) MSGTYPE(*ESCAPE) ENDPGM: + ENDPGM
Message Queue Housekeeper
Figure 3 Command Processing Program OBS001RG*=============================================================== * To compile: * * CRTRPGPGM PGM(XXX/OBS001RG) SRCFILE(XXX/QRPGSRC) * *=============================================================== *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 ...+... 7 IMSGBUF DS I B 1 40BYTRTN I B 5 80BYTAVL I 20 21 MSGTYP I 22 25 CURKEY I 56 81 QJOBNM IJOBBUF DS I 51 60 JOBSTS IERROR DS I I 0 B 1 40BYTPRV I DS I I 81 B 1 40MILEN I I 60 B 5 80JILEN I I 0 B 9 120MWT C *ENTRY PLIST C PARM QMSGQ 20 C MOVEL'*TOP' MSGKEY 4 C EXSR RCVMSG C EXSR CHKMSG * C BYTRTN DOWNE8 C BYTAVL ANDNE0 C EXSR RCVMSG C INQKEY IFNE *BLANKS C EXSR RMVMSG C ENDIF C EXSR CHKMSG C ENDDO * C MOVEL*ON *INLR *================================================================ C RCVMSG BEGSR *================================================================ C CALL 'QMHRCVM' 50 C PARM MSGBUF C PARM MILEN C PARM 'RCVM0200'FMT 8 C PARM QMSGQ C PARM '*NEXT' MTYPE 10 C PARM MSGKEY C PARM MWT C PARM '*SAME' MSGACT 10 C PARM ERROR C MOVELCURKEY MSGKEY C ENDSR *================================================================ C CHKMSG BEGSR *================================================================ C MSGTYP IFEQ '05' C MOVELMSGKEY INQKEY C EXSR RTVJOB C ENDIF C ENDSR *================================================================ C RTVJOB BEGSR *================================================================ C CALL 'QUSRJOBI' 50 C PARM JOBBUF C PARM JILEN C PARM 'JOBI0100'FORMAT 8 C PARM QJOBNM C PARM *BLANKS INTJID 16 C *IN50 IFEQ *OFF C JOBSTS ANDEQ'*ACTIVE' C MOVEL*BLANKS INQKEY C ENDIF C ENDSR *================================================================ C RMVMSG BEGSR *================================================================ C CALL 'QMHRMVM' 50 C PARM QMSGQ C PARM INQKEY 4 C PARM '*BYKEY' MSGTRM 10 C PARM ERROR C MOVEL*BLANKS INQKEY C ENDSR *. 1 ...+... 2 ...+... 3 ...+... 4 ...+... 5 ...+... 6 ...+... 7