What You Should Know About Activation Groups
February 11, 2004 Joel Cochran
In the last several articles we began dipping our toes into the ILE waters by learning about service programs, binder source, and binding directories. You should now have a firm grasp of these building blocks and be ready to start constructing an ILE application. But before plowing ahead, we need to examine the most frustrating and least understood aspect of ILE: activation groups.
WHAT IS AN ACTIVATION GROUP?
I like to think of an activation group as a container for memory and other resources. According to the ILE Concepts manual (PDF format), this container is considered a “substructure of a job.” In other words, the job owns the activation group. In fact, a job can own many activation groups.
So an activation group is a resource container within a job. Simple enough. But what does the container hold? The short answer is that it holds a bunch of activations, hence the term “activation” group. An activation is a reference to the storage allocation and runtime program binding activities that the operating systems perform when a program executes.
If it sounds complex, that’s because it is. The good news is that you don’t really have to understand these details in order to use activation groups. In fact, whether you realize it or not, you’re already are using activation groups! Your job has a DAG, or default activation group, that is automatically created when your job is started. Actually, two default activation groups are created, but we’ll treat them as one for the purpose of this article. This activation group is where your system code and OPM (Original Program Model) programs run. ILE programs can run in the default activation group, but I’ll come back to that in a bit.
Previous articles discussed creating programs out of modules, which is a two-step process: In RPG, this would include CRTRPGMOD (Create RPG Module) and then CRTPGM (Create Program). I use this approach exclusively, but for single-module programs, these steps can be consolidated into one command: CRTBNDRPG (Create Bound RPG Program). In PDM, this is the old stand by option 14, and the result is a *PGM object: The interim *MODULE object is not created. The reason why I mention this approach now is that this is typically the first run-in that new RPG IV programmers have with activation groups.
If you prompt 14 in PDM, you will see an option for Default Activation Group, with a default of *YES. If you try to create a program with DFTACTGRP(*YES), that program can’t use any ILE features: no procedures (not even internal), no service programs, no binding directories. This sort of program is considered OPM even if it is written in RPG IV. As a result, you frequently see examples that include an H-spec of DFTACTGRP(*NO). This allows the program to compile with all of those ILE goodies because now, by virtue of being created with something other than the DAG, it is an ILE program.
For clarification, I should point out that a program created from *MODULE objects, whether one or many, cannot be created with DFTACTGRP(*YES). In fact, even if you include DFTACTGRP(*NO) in a source member and try to create a module (option 15 in PDM), you will receive a compile error because DFTACTGRP is not a valid parameter for the CRTMOD command. This makes sense when you consider that only ILE programs can use *MODULE objects, so a non-ILE option cannot be allowed.
Now that we are firmly entrenched in ILE territory, we are going to leave the DAG behind and explore our ILE options. There are several approaches you can use when creating a program or service program.
The default for CRTPGM (and not an option for CRTSRVPGM) is ACTGRP(*NEW), so let’s start there. This means that every time the program is executed, it creates a new activation group, or container, for all the resources necessary to run the program. Memory is allocated, the program is copied into the activation group, variables are initialized, and so on: This is very similar to OPM behavior. When the program reaches termination, the activation group is deleted and the resources are reclaimed.
As you can probably guess, this can be a labor-intensive process for the operating system, especially if the program is executed frequently. If you have a program that you execute 50 times a day from the same job (remember this is all job-oriented!), you are creating the activation group, executing the program, and destroying the activation group 50 times. This adds a lot of overhead to your job and will certainly affect performance.
The most important thing to remember about *NEW is that every call to the program is an island unto itself: Variable values and file states are new and fresh every time the program is called, even if called recursively. As such, it is advisable to use *NEW sparingly.
The default for CRTSRVPGM is *CALLER. Any program or service program invoked with *CALLER will run inside the activation group of the calling program or procedure. This can really improve performance in your applications, because it removes the overhead of creating the activation group. Using *CALLER works well for most procedures that are activated by other procedures, which is why it is the logical default for service programs.
I mentioned earlier that ILE programs can run in the DAG. You do so by specifying *CALLER, then executing the program directly from a command line or calling it from an OPM program. Since you are in the DAG when you do so, the program executes within the DAG. This is generally considered a poor practice, for several reasons. First, it defeats the purpose of having activation groups by lumping all resources together into a single group. Second, it unnecessarily clutters up the DAG: Remember that the DAG’s main job is to run operating system code, so it seems logical that the less you can make that group worry about, the better. Finally, it prevents you from isolating the activities of an activation group because it doesn’t have a unique name, a topic that will be covered a little later.
The final option for activation groups is to give them a name. This is also the coolest option, and has the best promise for performance. A named activation group is just that: one for which you specify the name. The first time the program or service program executes, it goes through the overhead of creating the group, but then that group remains active until it is reclaimed or the job has ended. Now any subsequent time the program is called it will run in the already extant group. Since the activation group is already running, start up time can be greatly diminished. As promised, this means faster performance.
To illustrate this, let me tell a story on myself. When I first went live several years ago with my RPG-CGI Web site, www.vamanet.com, I noticed that it ran great for a few users but that performance seriously degraded with more visitors. There were lots of explanations for that scenario, and I think we tried every one we could think of, to no avail. As I have frequently done in the past, I turned to an e-mail list for assistance. After going around the barn a few times, someone asked about the activation group specified when the CGI programs were created. After that, it didn’t take long to realize my mistake: I had created all the programs with ACTGRP(*NEW).
By using *NEW, every Web request required the AS/400 to create an activation group, execute the program, answer the request, and destroy the activation group. That was a lot of overhead for something as simple as a Web page! Multiply that overhead by several thousand hits an hour, and my poor little 270 simply couldn’t keep up with the demand. The solution was simple: I recreated all the programs with ACTGRP(named), where named is the name of the CGI program. Now, within each CGI job, once a page has been requested the activation group remains and is constantly reused. This provided a dramatic increase in performance.
SERVICE PROGRAMS WITH ACTGRP(named)
So far I’ve primarily discussed programs, so I’ll shift a little and talk about service programs in particular. Again, the most common approach for CRTSRVPGM is to use ACTGRP(*CALLER). When you consider that most service programs consist of procedures that are called and used by other programs, it makes perfect sense to let the service program run in the same activation group as the program that’s calling it.
Now, if you have 100 programs in 100 activation groups all using the same procedure in service program A, there will be 100 activations of that service program. That could obviously hamper performance. To improve that, you could put your service program into a named activation group, so that all the calling activations are sharing the same service program activation. This would be worth examining if you have a service program that is used extensively. As always, though, there is a caveat.
Using a named activation group introduces another interesting feature: Any static or global variables you use in a service program with a named activation group are available across other activation group boundaries.
With PGM A and PGM B, each created with ACTGRP(named), both call procedure getHowMany() in SRVPGM C. The getHowMany() returns the number of times that the procedure has been called. The code for SRVPGM C looks something like this:
d howMany s 5i 0 inz( 0 ) d getHowMany pr 5i 0 p getHowMany b export d getHowMany pi 5i 0 /free howMany = howMany + 1 ; return howMany ; /end-free p getHowMany e
If you create service program C with *CALLER, PGM A and PGM B would have their own copies of the howMany variable within their own activation. Since howMany is defined outside of any procedures (see the D-spec coded above the procedure prototype), it is considered static or global. That means that on subsequent calls within the same activation, the value of howMany remains. If PGM A calls it 50 times, the returned value would be 50. If PGM B calls it 75 times, the value will be 75.
This behavior appears to change, however, if you create service program C with ACTGRP(named). Returning to the example above, now if PGM A calls it 50 times and PGM B calls it 75 times, the value will be 125! Now, since there is only one activation of the service program, howMany only exists once. While the behavior appears different, it is in fact the exact same: The difference is in which activation you are accessing.
This can be a very powerful feature because it allows you to share values like this across multiple activation groups within the same job. It could also get you into a lot of trouble if you didn’t realize this was going to happen when you created a service program with ACTGRP(named). I’d like to thank Barbara Morris of IBM for showing me this little nugget. Her advice helped me to come up with a very simple yet elegant approach to a particular solution I was developing.
A FEW MORE GOODIES
I’ll leave you this time with a couple of quick goodies about activation groups. If you have an activation group that is not being used, and you would like to get rid of it, you can use the Reclaim Activation Group (RCLACTGRP) command. Indicate ACTGRP(*ELIGIBLE) to delete all non-active but existing activation groups (not including the DAG) or indicate ACTGRP(name), where name is the specific activation group you want to delete.
So how do you see an activation group? Well, you can’t really see the activation group, but you can find a list of all the active groups for a particular job. Go to WRKJOB (or WRKACTJOB, and select option 5 for the job you want), and select option 18 (“display activation groups, if active”). You will see a listing of all the active groups associated with that job and a status indicator.
There is bunch more you can learn about activation groups, but I hope I’ve given you a good overview of the basics. For more information, I recommend that you check out the IBM manual ILE Concepts (PDF format) and the always helpful Redbook Who Knew You Could Do That with RPG IV? A Sorcerer’s Guide to System Access and More.
Joel Cochran is the director of research and development for a small software firm in Staunton, Virginia, and is the author and publisher of www.RPGNext.com. E-mail: email@example.com