Here’s a clear, practical guide to how activation groups and *INLR
interact in ILE RPG—and what that means for files, memory, ODPs, service programs, and repeated calls.
How they relate (mental model)
-
Activation group (AG) = a runtime container that holds loaded programs/service programs, their static storage, open files/ODPs, and other resources.
-
*INLR
(Last-Record indicator) = a program/module-level cleanup flag. When you set*INLR = *ON
and exit the program (e.g.,RETURN
), the system can close that program’s files, release its static storage, and mark the module to be removed from its activation group when no longer in use.
Key:
*INLR
affects the current program/module, not the entire activation group. The activation group can continue to live on with other programs and open resources.
What actually happens in common setups
1) Main RPG program in a named AG (e.g., actgrp('APPGRP')
)
-
Call #1: Program loads into
APPGRP
. Files open, ODPs created, static vars allocated. -
You set
*INLR = *ON; RETURN;
-
The program is eligible to be removed and its files/ODPs closed.
-
But the activation group remains alive (it can hold other modules/service programs).
-
-
Call #2 (same job): IBM i may reload the program (fresh static vars, fresh ODPs).
Good for long-running jobs where you want the group alive but each program call clean.
Gotcha: If other code in the same AG keeps a file open (e.g., a service program), that file/ODP can persist even though your main program set *INLR
.
2) Main RPG program with actgrp(*NEW)
-
Each call runs in its own transient AG.
-
*INLR = *ON; RETURN;
ends the program, and when the program ends, the AG ends, so everything is guaranteed clean (files closed, ODPs gone, static storage freed). -
Safest isolation; slightly higher startup overhead.
3) Service program usage (actgrp(*CALLER)
best practice)
-
Service program (SRVPGM) compiled with
actgrp(*CALLER)
runs inside the caller’s AG. -
Setting
*INLR
in the main program does not clean up the service program by itself—service programs don’t have*INLR
. Their static storage and any open files they own remain as long as the AG is alive (and the SRVPGM is still active). -
To avoid lingering state, ensure the SRVPGM closes files itself when appropriate or end the AG (see below).
What *INLR
does vs what it does NOT
*INLR
(when followed by an exit like RETURN
) does:
-
Mark the program for termination/cleanup.
-
Close files owned by that program (if not explicitly kept open).
-
Release the program’s static storage.
-
Remove the module from the AG when possible.
*INLR
does NOT:
-
End the activation group.
-
Close files opened by other modules/service programs in the same AG.
-
Affect subprocedures in service programs directly (they have no
*INLR
).
Always pair
*INLR = *ON
withRETURN
in a main program to actually exit now.
ODP (Open Data Path) & cursor behavior
-
In a shared/named AG, if the same file is opened later by another program or call and an ODP can be reused, you might see continued cursor position unless you reposition (
SETLL
,OPEN
with options, orCLOSE
). -
Using
*NEW
(isolated AG per call) or explicitlyCLOSE
at program end guarantees a fresh start on the next call.
SQL specifics (quick notes)
-
Embedded SQL cursors/resources are tied to the program and the AG.
-
*INLR
+RETURN
ends the program, but the AG may keep the SQL environment around if other modules in the same AG still use SQL. -
If you rely on a fully clean SQL state each run, prefer
actgrp(*NEW)
or end/reclaim the AG between calls.
Ending / reclaiming activation groups
Sometimes you need to ensure everything is flushed, not just the current program:
-
End this program cleanly:
In RPG:*inlr = *on; return;
-
Reclaim a named activation group (from CL):
RCLACTGRP ACTGRP(APPGRP)
Frees all resources in that AG (files, service programs, static storage).
-
Reclaim all eligible groups in the job:
RCLACTGRP ACTGRP(*ELIGIBLE)
Use with care in long-running jobs; reclaiming can drop shared caches/state by design.
Practical patterns & recommendations
Goal | Recommended setup | Why |
---|---|---|
Clean slate each call | actgrp(*NEW) |
Automatic full cleanup; simplest mental model. |
Fast repeated calls within a job but clean program exits | Named AG for the app (e.g., actgrp('APP') ) + in main program *INLR = *ON; RETURN; + ensure SRVPGMs close files |
Keeps server performance while avoiding most residue. |
Central services used by many callers | SRVPGM with actgrp(*CALLER) and no file state leak |
Lets caller control lifecycle; avoids sticky state. |
Debug mysterious READ/SETLL/CHAIN skips | Ensure *INLR used + explicit CLOSE before exit, or run under *NEW |
Eliminates ODP reuse surprises. |
Tiny code examples
A) Main program—safe exit in named AG
ctl-opt actgrp('APP') dftactgrp(*no);
dcl-f CUSTF keyed usage(*input);
read CUSTF;
// process...
// clean exit
*inlr = *on;
return;
B) Service program—no AG of its own, no file leaks
// SRVPGM module
ctl-opt actgrp(*caller) dftactgrp(*no);
dcl-f LOGF usage(*update: *output);
// Exported proc
dcl-proc LogIt export;
// write log
write LOGREC;
// optional: close if you never want ODP reused
// close LOGF;
end-proc;
C) For guaranteed clean runs in batch
ctl-opt actgrp(*new) dftactgrp(*no);
Common pitfalls to avoid
-
Using
RETURN
without*INLR
in a main program: the program stays resident; files/ODPs and static storage may persist → next call starts mid-stream. -
Assuming
*INLR
ends everything: it ends your program, not the AG; service programs and other modules may keep resources alive. -
Service program opens a file and never closes it in a long-lived named AG: future callers see unexpected ODP/cursor state.