Avoiding the Green Screen of Death in RPG Programs
April 5, 2006 Ted Holt
Although I have been using various versions of Microsoft Windows since the early 1990s, I am still not impressed when I gaze upon that phenomenon popularly known as the “Blue Screen of Death.” In the same way, I do not think iSeries users are impressed when they see the Display Program Messages panel, or as I call it, the “Green Screen of Death.” Fortunately, showing users the Display Program Messages panel is almost always avoidable. When a program encounters an error that it has not been told how to handle, it stops. For interactive jobs, the user is presented with the Display Program Messages panel. For batch jobs, the system operator receives an inquiry message, which must be answered. Any jobs waiting in the job queue for their turn to run are delayed. In either case, someone has to decide how to proceed. I have already written about error-handling in CL. The trick is to always include a global MONMSG command and to send an escape message when an unexpected or unrecoverable error occurs. Here are a few tips for handling unexpected and unrecoverable errors in the other language I use–RPG. Open Files by Hand If you allow the RPG cycle to open files, you have no way to trap file open errors. If a file does not exist, for instance, the program generates a hard error and there is nothing you can do about it. In order to trap bad opens, you must open the files by hand. This means you specify the USROPN option on all F specs and use the OPEN op code. Don’t Use *PSSR If you include a subroutine named *PSSR in your program, the subroutine will assume control if an unexpected error occurs that is not related to a file. For example, *PSSR will rear its ugly head if you access a missing parameter or a packed decimal field that has invalid data in it. However, it is difficult to recover from the error and continue at a place of your choosing in the program from *PSSR. Don’t Use INFSR You can use the INFSR keyword subroutine on an F spec, the subroutine you name as a parameter will be invoked in the event of a file error. This technique avoids the Green Screen of Death, but I don’t use it anymore. For one thing, it annoys me to have to write subroutines to handle file errors. But what’s worse is that I can’t use a file in a subprocedure if the INFSR keyword is coded for the file, and I do like to use subprocedures. Do Use MONITOR The MONITOR op code is the newest way to trap errors in RPG. In my opinion, it’s the best way. I like it better than using the *PSSR and INFSR subroutines, because it provides better recovery. That is, I can more easily control how the program will continue after an error. I also prefer MONITOR to the coding of an E extender on op codes, although using the E extender is not a bad method. And I greatly prefer MONITOR to the use of an error indicator in the “LO” resulting indicator position, mainly because I avoid the use of indicators and because free-format calculations do not allow for resulting indicators. Here are a few examples of how a programmer might use MONITOR to trap unexpected errors. These are not the only possible techniques, but they are what I consider some good ones. Each example attempts to add a new record to a file called XACTS. * File XACTS: some transaction file A UNIQUE A R XACTREC A ID 5P 0 A NAME 12A A K ID Values for the new record are passed into the program through parameters. First, for comparison, here is program SomePgm with no error handling. H dftactgrp(*no) actgrp(Whatever) FXACTS o e disk * ===== *ENTRY PLIST ===== D SomePgm pr D inID 5p 0 D inName 12a D SomePgm pi D inID 5p 0 D inName 12a /free *inlr = *on; ID = inID; Name = inName; write XactRec; return; The program accepts two parameters–inID and inName–and attempts to add a new record to the XACTS file. Many things can go wrong.
Here’s my first go at adding some error-handling to SomePgm. I have placed the program logic into a subroutine named Main. The main calculation section runs subroutine Main under control of MONITOR. H dftactgrp(*no) actgrp(Whatever) FXACTS o e disk usropn * ===== *ENTRY PLIST ===== D SomePgm pr D inID 5p 0 D inName 12a D SomePgm pi D inID 5p 0 D inName 12a D/copy prototypes,assert D/copy qrpglesrc,psds /free *inlr = *on; monitor; exsr Main; on-error; assert (*off: 'Unexpected error in program ' + %trim(psdsProcName) + '. See job log for details.'); endmon; return; // =========================== begsr Main; open xacts; ID = inID; Name = inName; write XactRec; close xacts; endsr; I did not know it until Barbara Morris of IBM told me, but when you run a subroutine under the control of MONITOR, everything in the subroutine is monitored. Therefore, MONITOR catches any error–file error, decimal data error, missing parameter error, and so on–and invokes the ON-ERROR calculations. In this example, I used the ASSERT routine to end the program with an escape message. (To learn more about assertions, see Programming with Assertions.) The assertion refers to psdsProcName, which is a subfield of the program status data structure. My PSDS copy member defines the entire data structure, but you can use this PSDS member if you’d like to run these examples. D psds sds D psdsProcName 1 10a A similar technique is to move the program logic into a subprocedure. I think I like this method a little better, as I continue to try to move away from the use of subroutines and toward the use of subprocedures. (I wrote about the advantages of subprocedures in my article Subprocedures: Better than Subroutines.) Here’s the same example with the main calculations moved into a subprocedure. H dftactgrp(*no) actgrp(Whatever) FXACTS o e disk usropn * ===== *ENTRY PLIST D SomePgm pr D inID 5p 0 D inName 12a D SomePgm pi D inID 5p 0 D inName 12a D/copy prototypes,assert D/copy qrpglesrc,psds D SomePgm_Main pr /free *inlr = *on; monitor; SomePgm_Main(); on-error; assert (*off: 'Unexpected error in program ' + %trim(psdsProcName) + '. See job log for details.'); endmon; return; // =========================== /end-free P SomePgm_Main b /free open xacts; ID = inID; Name = inName; write XactRec; close xacts; /end-free P e Since the program is named SomePgm, I named the subprocedure SomePgm_Main. If anything goes wrong in the subprocedure, the ON-ERROR kicks in and runs the assertion, canceling the program. I like the way this works, but I would like to see one more improvement. I would like to have more meaningful error messages. That is, instead of one error message that tells me that something went wrong, I would like to have more specific error messages. I can achieve this goal by adding other MONITOR op codes to handle specific problems. In the following version, I have added monitors to handle the open and write operations. H dftactgrp(*no) actgrp(Whatever) FXACTS o e disk usropn * ===== *ENTRY PLIST D SomePgm pr D inID 5p 0 D inName 12a D SomePgm pi D inID 5p 0 D inName 12a D/copy prototypes,assert D/copy qrpglesrc,psds D SomePgm_Main pr /free *inlr = *on; monitor; SomePgm_Main(); on-error; assert (*off: 'Unexpected error in program ' + %trim(psdsProcName) + '.'); endmon; return; // =========================== /end-free P SomePgm_Main b /free monitor; open xacts; on-error; assert (*off: 'File open failed in program ' + %trim(psdsProcName) + '.'); endmon; ID = inID; Name = inName; monitor; write XactRec; on-error; assert (*off: 'Write failed in program ' + %trim(psdsProcName) + '.'); endmon; close xacts; /end-free P e So, is it still possible to get the Green Screen of Death after adding so much monitoring to the program? Yes, it is! If an operation under the ON-ERROR of the main calculations fails, the user gets the Display Program Messages panel. But I don’t see this as a problem. I see it as a last resort. After all, if something goes wrong, someone needs to know about it. If my attempts to handle the error gracefully with escape messages fails, then a human will be notified. However, I have never had an assertion (in RPG) or a SNDPGMMSG command (in CL) to fail. |
When an error occurs that is already handled by a monitor block, then RPG treats it as the error is handled so much that it will not respond to another monitor block which may be immediately following or one that calls this proc or SR (what you termed as ‘global monitor’). In the last example in this article, if open xacts fails the monitor of that block will handle error but monitor block immediately following *lr will not. If there are code statements following somepgm_main() then they WILL be executed which, I think, should not happen. The way to handle this is: have a variable in the SR to indicate to global monitor that an error occurred and take care of it further.