A very real behavior in IBM i ILE RPG where activation groups and Open Data Paths (ODPs) affect how file reads behave across programs.
๐ฏ Scenario Summary
-
Two programs: PGM1 and PGM2
-
Both are compiled with the same activation group (e.g.,
'SALEGROUP') -
Both use the same file, say
SALESF -
PGM1 does
SETLL+READonSALESF -
Then PGM2 is called and also does
READonSALESF, without SETLL
๐ง What Happens
1. File sharing occurs via the same activation group
Because PGM1 and PGM2 share the same activation group, IBM i reuses the Open Data Path (ODP) for SALESF. That means:
-
The file cursor/position is shared
-
The file stays open across program calls
2. PGM2 continues reading where PGM1 left off
So in your case:
-
PGM1does:setll key SALESF; read SALESF; // reads record #1 read SALESF; // reads record #2 -
Then
PGM2does:read SALESF; // continues from record #3
Because PGM2 never did SETLL, it just continues reading from wherever PGM1 left off.
โ Behavior: This is normal and expected in shared activation groups
| Shared Component | Behavior |
|---|---|
| File ODP | Shared |
| File cursor | Shared (position retained) |
| File state | Shared (open, locked, etc.) |
| Buffer, vars | Not shared unless explicitly passed |
๐ Potential Pitfalls
| Issue | Risk |
|---|---|
Forgetting to SETLL in PGM2 |
Unexpected record or skipped data |
| Assuming files are "fresh" per program | Leads to bugs in batch or API jobs |
| Files left open | Can block others or cause locking issues |
โ Best Practices
| Practice | Why |
|---|---|
Always SETLL or CHAIN before READ |
Ensures correct positioning |
Use CLOSE if you want a clean state |
Frees ODP and file cursor |
Use ACTGRP(*NEW) if you want isolation |
Avoids shared file behavior |
| Document shared file behavior | Helps in modular/multi-program flows |
๐ก Bonus Tip: Detecting ODP Reuse
You can trace ODP usage with a debug tool or use debug logging to monitor how many times a file is opened and where it starts reading.
Below is a working demo of two RPGLE programs (PGM1 and PGM2) using the same activation group and sharing a physical file (SALESF). The goal is to demonstrate how PGM2 reads from where PGM1 left off.
๐งพ Step 1: Physical File — SALESF
Create this DDS source:
A R SALESREC
A SALEID 5A
A AMOUNT 9P 2
A K SALEID
Compile it with:
CRTPF FILE(MYLIB/SALESF) SRCFILE(MYLIB/QDDSSRC)
And insert some sample data (via SQL or DFU):
INSERT INTO SALESF VALUES ('S0001', 100.00);
INSERT INTO SALESF VALUES ('S0002', 200.00);
INSERT INTO SALESF VALUES ('S0003', 300.00);
INSERT INTO SALESF VALUES ('S0004', 400.00);
๐งพ Step 2: Program PGM1 — READ with SETLL
ctl-opt actgrp('SALEGROUP') dftactgrp(*no);
dcl-f SALESF keyed usage(*input);
dcl-s saleid char(5);
dcl-s amount packed(9:2);
setll 'S0001' SALESF;
read SALESF;
if not %eof(SALESF);
saleid = SALEID;
amount = AMOUNT;
dsply ('PGM1: ' + saleid + ' - ' + %char(amount));
endif;
read SALESF;
if not %eof(SALESF);
saleid = SALEID;
amount = AMOUNT;
dsply ('PGM1: ' + saleid + ' - ' + %char(amount));
endif;
// Now call PGM2 (reads same file without SETLL)
call 'PGM2';
๐งพ Step 3: Program PGM2 — READ Without SETLL
ctl-opt actgrp('SALEGROUP') dftactgrp(*no);
dcl-f SALESF keyed usage(*input);
dcl-s saleid char(5);
dcl-s amount packed(9:2);
// No SETLL โ continues from where PGM1 left off
read SALESF;
if not %eof(SALESF);
saleid = SALEID;
amount = AMOUNT;
dsply ('PGM2: ' + saleid + ' - ' + %char(amount));
endif;
read SALESF;
if not %eof(SALESF);
saleid = SALEID;
amount = AMOUNT;
dsply ('PGM2: ' + saleid + ' - ' + %char(amount));
endif;
๐งช Expected Output
PGM1: S0001 - 100.00
PGM1: S0002 - 200.00
PGM2: S0003 - 300.00
PGM2: S0004 - 400.00
Notice how PGM2 picks up exactly where PGM1 left off, because the file ODP and cursor are shared via the same activation group.
โ
Optional: Isolate Behavior with *NEW
If you change actgrp('SALEGROUP') to actgrp(*NEW) in either program, the file will be reopened, and the cursor will reset — so PGM2 would start from the top again.