Although they've been around for quite a while, activation groups remain something of an enigma; this article will begin to dispel that mystery.
When RPG ILE was introduced back in 1994, it really consisted of two entirely different components: syntactical changes to the RPG language itself, and the larger underlying change of the entire programming model with the introduction of the Integrated Language Environment (ILE). As usual, IBM did a good job of hiding the complexities of the fundamental architecture change (CISC to RISC, anyone?). So good, in fact, that for a lot of us more "seasoned" programmers, the ILE concepts sort of got overshadowed by the syntactical changes. A lot of it was practical: we got a lot more out of learning the new built-in functions (BIFs) than we did out of activation groups. But I think it's high time that we all learned what activation groups can do for us, and there's no easier way to start than with a simple, practical example.
Effective Use of *INLR
*INLR, the last record indicator, has been an effective tool for decades. I've taken advantage of this particular feature since my System/38 days. By leaving *INLR off when returning from a called program, the next call to that program is significantly faster. The primary underlying reason for this is that the program doesn't get re-initialized. And while speed is a principal benefit of this process, the technique also provides a number of potentially powerful side effects. For example, files stay open and variables keep their values between calls. This can be very powerful.
Let's take a simple example. Let's say I have a file with a small number of records with relatively static contents—contents that would be expected to stay the same for the lifetime of a job. An example might be a set of business rules or perhaps a template for formatting output. In either case, the database might change between runs of the job but not during a single run. During the job, these records will be accessed over and over again. Rather than access the file directly, this is an excellent opportunity to use a preloaded table. I call a program to get the appropriate data from the table, and the first time I call the program, I read the entire file into an array of data structures. From that point forward, anytime I need one or more records from the file, I just do a quick lookup in the array. Simple and very efficient.
And this isn't the only example of returning without setting on LR. Another case might be calling an RPG program to return a list of records. RPG isn't particularly good about handling dynamic arrays, but languages like Java and EGL are, so one of the design patterns I use throughout my multi-tiered applications is to write a service routine (in the Java/EGL layer) that calls the RPG program in a loop. The first call sets the initial keys and attempts to retrieve a record, while subsequent calls return additional records until I hit the end of the list. By not setting on *INLR, I simply need to do a READE (for RPG; I would use a FETCH NEXT when the program is written using embedded SQL). Each additional call requires literally milliseconds, so I can populate lists quickly and easily. Imagine the overhead of a stateless session in which I had to send a key each time, opening a file (or worse yet, a cursor) and then fetching the next record. A stateful approach will perform far better.
Cleaning Up at Closing Time
So what's the downside to this technique? Probably the biggest problem is file locking. If you never end the program, the file will have an object lock that could affect other processes. Another issue is that sometimes you do have to re-initialize the program, which means you have to force the end of the program.
Each problem is more or less prevalent depending on the application, and solutions exist for each one. For example, table preloads can reasonably close their primary files after loading the tables; this gets around the record lock issue but not the re-initialization. Query programs can set on LR when they hit end-of-file; while it does add some overhead, it may not be significant, especially if the program is embedded SQL with an expensive initial cursor build. But in general, the best solution is to have a way to signal to those programs that they should shut down by setting on LR and exiting.
The easiest way I've found for this is to use the parameter list. Let's say I have a program called CHKCTRY that checks a country code against a table and does not set on LR. I have an order entry program ORDENT that calls CHKCTRY. When ORDENT ends, I make one additional call to CHKCTRY with no parameters. CHKCTRY has this code at the beginning of its mainline.
// Check for shutdown request
if %parms = 0;
*inlr = *on;
If no parameters are passed, set on LR and get out. Very simple and neat. I suppose this wouldn't work if the called program had no parameters, but then again I rarely call subprograms that don't require parameters. Even if I did, I could reverse the logic: if I do pass a parameter, then I set on LR and exit. Anyway, you get the idea: a special last call will shut down the stateful called program.
The problem is that I need to add that last call. And as I add more and more subprograms, I have to add a bunch of additional calls. It gets even worse if those programs call other programs! In fact, I had a whole utility system in which programs would register themselves with a called program controller, and when the main program ended, it would call the controller, which would in turn call each of the registered subprograms and tell them to shut themselves down.
Enter the Activation Group!
All of that complexity goes away with the activation group. In many ways, an activation group is a subset of the job, and when you shut down the activation group (an operation known as "reclaim" in ILE terminology), all of the programs within the activation group shut down as well. It's as if each program is set on LR and returned: files are closed, and on subsequent calls to the program, all variables are reinitialized. It's the exact equivalent to the LR technique we used in the pre-ILE days but cleaner and easier. I don't have to keep track of all the called programs; the operating system does that for me.
Implementing the activation group technique requires basic knowledge of ILE. I won't spend much time on the theory; instead I'll just give you a concrete example along with an explanation of how it works. I like to use one specific activation group technique: *NEW and *CALLER. In this architecture, the main program is compiled with the attribute of ACTGRP(*NEW), and whenever that program is called (typically from a main menu), the operating system creates a new activation group. All of the called programs are compiled with the attribute of ACTGRP(*CALLER) so that they inherit the activation group of the main program. When the main program ends, it sets on LR and exits, which triggers the reclamation of the activation group that was created when the main program was first called. This in turn shuts down each of the called programs. Simple, efficient, and automatic. I like that.
The other type of activation group used in ILE programming is the named activation group. The technique outlined here works with a named activation group, provided all the called programs were either in the same activation group or were marked as *CALLER. The problem is that you have to explicitly reclaim named activation groups. It's not difficult, but it's yet one more thing you have to name and keep track of, and try as I might, I can't understand why someone would go to that trouble if they didn't have to.
Let me leave you with a note of caution: a final twist is the so-called "default activation group"; this is the activation group where non-ILE (also known as Old Program Model, or OPM) programs run. It's a bad neighborhood for ILE programs, so we'll avoid it whenever possible. Even so, it's relatively easy to protect yourself: the only way you can really get yourself into trouble is if your OPM programs call ILE programs compiled with ACTGRP(*CALLER). Don't do it!
Thanks for letting me introduce you to activation groups. It's a first step into the world of ILE, and I hope I can provide you more guidance in the future.
as/400, os/400, iseries, system i, i5/os, ibm i, power systems, 6.1, 7.1, V7,