Old Stuff, New Ways: Avoiding Record Locks
March 3, 2015 Jon Paris
Note: The code accompanying this article is available for download here.
Recently I was teaching a group of RPGers the joys of qualified data structures. I happened to mention how much simpler some of the new DS capabilities had made the techniques I use to avoid problems caused by record locks. It turned out that more than half of the audience had never heard of the technique. I guess that we all have a tendency to think that the techniques and tools that we use are common knowledge amongst other programmers. As an educator I should perhaps be less prone to this tendency than others, but nevertheless I often fall into this trap. With that realization came the thought that perhaps a tip or two on modern versions of such techniques would be a good idea.
This first tip involves that record lock technique. The intent of the technique is to avoid situations where a user displays a record for update, and then goes off to the bathroom, break room, or whatever, and leaves the terminal signed on with the record locked. This can often cause another application to come to a grinding halt when it encounters the locked record. For this reason I’ve always believed it to be a good idea not to lock the record when it is being displayed, but rather only lock it during the actual update process, i.e., when the user submits the modified data.
The disadvantage to this approach, of course, is that there is a possibility that the record could be changed by another process during the interval between it being initially displayed to the user and the subsequent update operation being performed. In order to protect against this the program maintains a copy of the original record and compares it with the version retrieved immediately prior to performing the update. In the event that the data has changed, an error message is sent to the user to inform them and ask them to redo the update if necessary. If there was no change in the data, then the update can proceed. For years I used multiple-occurrence data structures (MODS) in order to implement this technique, but switched to the version I’m discussing here as soon as DS arrays and DS I/O became available back in the V5R2 timeframe.
So, the basic process is to read the record, without locking it, into DS array element 1. The data is then displayed to the user for update. Once the user requests the update, then the record is read again, this time into element 2. Elements 1 and 2 are now compared for equality. If they match then the update proceeds. If they do not, the user is notified and the latest version of the data presented.
That’s the basic technique, now let’s look at the code that implements it. Note that while I have used fixed-format D specs in this text, the download version of the source includes a fully free version. (There has to be a better term that that!).
Let’s begin by looking at the two data definitions that play a major role. At (A) you can see that I’ve defined a two element data structure array named custRec using the LikeRec keyword. At (B) the record for the display file fields is defined. This is also a likeRec DS and, as you will see later, defining it in this fashion makes it easy to copy the required data from the database record to the display format and vice versa thanks to EVAL-CORR.
(A) D custRec DS Likerec(TestRec: *All) Dim(2) (B) D displayRec DS LikeRec(Disprec: *All)
Moving on to the logic for loading and displaying a record, there are two things to note about the CHAIN. First is the use of the opcode extender N, which tells RPG not to lock the record even though the file is specified for update. The second is the use of the first custRec array element as the result field. This feature, in case you have not seen it before, has been available for externally described files since V5R2. I will briefly describe some of its other benefits later in this tip.
If the requested record is found the EVAL-CORR (D) is used to load all the display file fields from the record image.
(C) Chain(N) displayRec.ARCode TestFile custRec(1); If %Found(TestFile); (D) Eval-corr displayRec = custRec(1);
The real meat of the process takes place in the update subprocedure. We begin at (E) by once again chaining to the file but note that this time the record is loaded into the second element of the array. This enables us to compare (F) the initial version of the record, i.e., the one we displayed to the user, with the latest version. Notice also that we did not use the N extender on this chain and therefore the record will have been locked for update. If the two record images are identical then the update can proceed. Again Eval-Corr (G) is used to copy the updated information from the display record to database image one. This image is then used to update the file.
If the two images were not identical, then the user is advised that the record has been updated since it was originally displayed, and is prompted to try again. The important part of this process is shown at H. First the current record content is copied to the display file and then record array element 1 is also set to the current record content so that it is ready for comparison if and when the operator requests an update. Last but not least, the record lock is released (I) so that we don’t create the very situation we were trying to avoid. That’s the entire process.
(E) Chain displayRec.ARCode TestFile custRec(2); // Read and lock record (F) If custRec(1) = custRec(2); // Compare with original // If no changes then update can proceed (G) eval-corr custRec(1) = displayRec; // Update record image Update TestRec custRec(1); ... Else; ... (H) eval-corr displayRec = custRec(2); // Reset display screen custRec(1) = custRec(2); // and set image 1 to current record (I) Unlock TestFile;
Benefits Of Data Structure I/O
For those of you unfamiliar with the use of DS I/O, there are a number of benefits that make it worth your consideration.
First, as demonstrated in this example, it is a simple matter to retain multiple instances of a record either by using an array or simply by using different data structures. No need to copy the data before reading another record.
Second, in many cases the I/O operation will be faster than when using the conventional RPG approach. The reason for this is simply that it is faster to move a large number of bytes from the record buffer to the DS than it is to move each of the fields individually.
Third, it can help you avoid decimal data errors. But that is a whole topic in itself and will be the subject of my next tip.
Jon Paris is one of the world’s most knowledgeable experts on programming on the System i platform. Paris cut his teeth on the System/38 way back when, and in 1987 he joined IBM’s Toronto software lab to work on the COBOL compilers for the System/38 and System/36. He also worked on the creation of the COBOL/400 compilers for the original AS/400s back in 1988, and was one of the key developers behind RPG IV and the CODE/400 development tool. In 1998, he left IBM to start his own education and training firm, a job he does to this day with his wife, Susan Gantner–also an expert in System i programming. Paris and Gantner, along with Paul Tuohy and Skip Marchesani, are co-founders of System i Developer, which hosts the new RPG & DB2 Summit conference. Send your questions or comments for Jon to Ted Holt via the IT Jungle Contact page.