Un travail batch bloqué : LCKW, MSGW et RNQ1218
La chaîne de nuit devait lancer 7 programmes à la suite. Le matin, elle est toujours là, arrêtée au troisième. Nous avons reproduit la panne sur un IBM i 7.5, avec des programmes RPG en full free, des fichiers physiques et logiques, puis suivi le diagnostic écran par écran jusqu'à la relance.
En une phrase
Un travail batch qui lit un enregistrement pour le modifier attend qu'aucun
autre travail ne le tienne : il passe en LCKW le temps du délai
WAITRCD du fichier, puis, si le verrou n'est toujours pas libéré, le programme RPG
s'arrête sur le message RNQ1218 et le travail reste en MSGW
jusqu'à ce qu'un opérateur réponde.
La chaîne de nuit
Un programme pilote, NUITCHAINE, lance sept programmes l'un après l'autre et note
chaque lancement et chaque fin d'étape dans un fichier journal :
| Étape | Programme | Rôle | Fichiers |
|---|---|---|---|
| 1 | NUIT01INI | Ouvre la journée : compte les clients et les commandes saisies. | CLIENTP, CDEL1 |
| 2 | NUIT02VAL | Valide les commandes saisies (état S → V). | CDEP |
| 3 | NUIT03ENC | Ajoute le montant des commandes validées à l'encours de chaque client. | CDEL1, CLIENTP en mise à jour |
| 4 | NUIT04FAC | Crée une facture par commande validée (état V → F). | CDEP, FACTP |
| 5 | NUIT05STA | Statistiques : encours total et plus gros client. | CLIENTL1 |
| 6 | NUIT06PUR | Supprime les commandes annulées (état A). | CDEP |
| 7 | NUIT07FIN | Clôture : compte les factures du jour. | FACTP |
Les données sont dans quatre fichiers physiques décrits en DDS et deux fichiers logiques :
CLIENTP | PF | Clients, clé CLNUM. Créé avec WAITRCD(120) : 2 minutes d'attente sur un verrou. |
|---|---|---|
CLIENTL1 | LF | Les clients par nom. |
CDEP | PF | Commandes, clé CDNUM, état S (saisie), V (validée), F (facturée), A (annulée). |
CDEL1 | LF | Les commandes par état, client et numéro. |
FACTP | PF | Factures, clé FANUM. |
JOURNP | PF | Le journal de la chaîne : une ligne par lancement et par fin d'étape. |
Le pilote appelle les programmes par un seul prototype dont le nom est une variable
(extpgm(nomPgm)). Son fichier journal est déclaré block(*no) : chaque ligne
est écrite tout de suite, et reste lisible même si la chaîne ne va pas au bout.
Le pilote NUITCHAINE (RPG full free)
**free
// ---------------------------------------------------------------------
// NUITCHAINE : pilote du traitement de nuit
// Lance les 7 programmes de la chaîne l'un après l'autre et trace
// chaque lancement dans le journal JOURNP et dans l'historique du travail.
// ---------------------------------------------------------------------
ctl-opt dftactgrp(*no) actgrp(*new) option(*srcstmt:*nodebugio);
// block(*no) : chaque ligne est écrite tout de suite, visible même si
// la chaîne reste bloquée
dcl-f JOURNP usage(*output) block(*no);
dcl-s nomPgm char(10);
dcl-pr lancer extpgm(nomPgm) end-pr;
dcl-s programmes char(10) dim(7);
dcl-s etape int(5);
programmes(1) = 'NUIT01INI'; // ouverture de la journée
programmes(2) = 'NUIT02VAL'; // validation des commandes saisies
programmes(3) = 'NUIT03ENC'; // mise à jour de l'encours client
programmes(4) = 'NUIT04FAC'; // facturation des commandes validées
programmes(5) = 'NUIT05STA'; // statistiques clients
programmes(6) = 'NUIT06PUR'; // purge des commandes annulées
programmes(7) = 'NUIT07FIN'; // clôture de la journée
journal(0: 'NUITCHAINE': 'Début de la chaîne de nuit');
for etape = 1 to %elem(programmes);
nomPgm = programmes(etape);
journal(etape: nomPgm: 'Lancement');
snd-msg *info 'Étape ' + %char(etape) + ' : lancement de ' + %trim(nomPgm);
lancer();
journal(etape: nomPgm: 'Terminé');
endfor;
journal(0: 'NUITCHAINE': 'Chaîne de nuit terminée normalement');
snd-msg *info 'Chaîne de nuit terminée normalement';
*inlr = *on;
// ---------------------------------------------------------------------
// Écrit une ligne dans le journal de la chaîne
// ---------------------------------------------------------------------
dcl-proc journal;
dcl-pi *n;
pEtape int(5) const;
pPgm char(10) const;
pMsg varchar(60) const;
end-pi;
joheure = %timestamp();
joetape = pEtape;
jopgm = pPgm;
jomsg = pMsg;
write journr;
end-proc;
L'étape 3 parcourt les commandes validées par client (logique CDEL1) et relit chaque
client pour mise à jour : c'est ce CHAIN, ligne 20, qui va
attendre.
NUIT03ENC, l'étape 3
**free
// ---------------------------------------------------------------------
// NUIT03ENC : étape 3, mise à jour de l'encours client
// Parcourt les commandes validées par client (logique CDEL1) et ajoute
// leur montant à l'encours du client (CLIENTP lu pour mise à jour).
// Si un autre travail tient la fiche d'un client, le CHAIN attend
// (WAITRCD du fichier) puis le programme s'arrête sur RNQ1218.
// ---------------------------------------------------------------------
ctl-opt dftactgrp(*no) actgrp(*caller) option(*srcstmt:*nodebugio);
dcl-f CDEL1 usage(*input) keyed;
dcl-f CLIENTP usage(*update) keyed;
dcl-f JOURNP usage(*output) block(*no);
dcl-s nbMaj int(10);
setll 'V' CDEL1;
reade 'V' CDEL1;
dow not %eof(CDEL1);
chain cdcli CLIENTP;
if %found(CLIENTP);
clencours += cdmont;
clnbcde += 1;
update clientr;
nbMaj += 1;
endif;
reade 'V' CDEL1;
enddo;
journal(%char(nbMaj) + ' commandes ajoutées à l''encours client');
*inlr = *on;
dcl-proc journal;
dcl-pi *n;
pMsg varchar(60) const;
end-pi;
joheure = %timestamp();
joetape = 3;
jopgm = 'NUIT03ENC';
jomsg = pMsg;
write journr;
end-proc;
Le coupable
Pour provoquer la panne, un autre travail joue l'utilisateur parti déjeuner en laissant la fiche du client 3 ouverte en modification : il la lit pour mise à jour, et ne la relâche jamais.
SAISIECLI
**free
// ---------------------------------------------------------------------
// SAISIECLI : simule un utilisateur parti déjeuner en laissant la fiche
// du client 3 ouverte en modification. Le CHAIN pour mise à jour pose
// un verrou d'enregistrement qui n'est jamais relâché, puis le travail
// attend 30 minutes.
// ---------------------------------------------------------------------
ctl-opt dftactgrp(*no) actgrp(*new) option(*srcstmt:*nodebugio);
dcl-f CLIENTP usage(*update) keyed;
dcl-pr qcmdexc extpgm('QCMDEXC');
cmd char(200) const;
lng packed(15:5) const;
end-pr;
dcl-s numClient zoned(6:0) inz(3);
chain numClient CLIENTP;
qcmdexc('DLYJOB DLY(1800)': 16);
*inlr = *on;
Les deux travaux sont soumis par SBMJOB. Le coupable va dans QSYSNOMAX : la file QBATCH de notre système ne fait tourner qu'un travail à la fois, et la chaîne aurait attendu derrière lui sans même démarrer (voir MAXACT).
/* Le coupable : garde la fiche du client 3 en mise à jour, puis attend 30 minutes */
SBMJOB CMD(CALL PGM(JOBIBLOC/SAISIECLI)) JOB(SAISIECLI) +
JOBD(JOBIBLOC/SIMJOBD) JOBQ(QSYSNOMAX) INLLIBL(*JOBD)
/* La chaîne de nuit : 7 programmes à la suite */
SBMJOB CMD(CALL PGM(JOBIBLOC/NUITCHAINE)) JOB(CHAINENUIT) +
JOBD(JOBIBLOC/SIMJOBD) INLLIBL(*JOBD)

INLLIBL(*JOBD) n'est pas un détail : sans lui, SBMJOB donne au travail la liste de bibliothèques de la session, pas celle de la JOBD.INLLIBL(*JOBD), notre chaîne s'est
arrêtée tout de suite, en MSGW sur un RNQ1216 : message CPF4101 pendant l'ouverture de JOURNP, fichier introuvable. La valeur par défaut de SBMJOB est
INLLIBL(*CURRENT), et la session qui soumettait n'avait pas la bibliothèque dans sa
liste (le piège est détaillé ici).
Le diagnostic, écran par écran
1. Le travail est en LCKW
Quelques secondes après la soumission, WRKACTJOB montre la chaîne dans QBATCH,
en état LCKW : elle attend un verrou.

WRKACTJOB SBS(QBATCH) : CHAINENUIT en LCKW. La fonction affiche PGM-NUITCHAINE, le programme lancé par le travail, et non celui qui attend.2. Où est-il arrêté ? La pile d'appels
La colonne Fonction ne nomme que le pilote. Pour savoir quel programme attend, et à quelle instruction : option 5 (gérer le travail), puis 11 (liste d'appel des programmes).

CHAIN. En dessous, les routines système de la lecture par clé.Option 12 (verrouillages du travail), puis F10 : les verrous d'enregistrements.

CLIENTP.3. Qui tient l'enregistrement ?
DSPRCDLCK sur le fichier répond directement :

DSPRCDLCK FILE(JOBIBLOC/CLIENTP) : SAISIECLI tient l'enregistrement 3, CHAINENUIT l'attend.En SQL, la vue QSYS2.RECORD_LOCK_INFO donne la même réponse (relevé lors d'un second passage, après remise à zéro des données) :
SELECT RELATIVE_RECORD_NUMBER AS RRN, LOCK_STATE, LOCK_STATUS, JOB_NAME
FROM QSYS2.RECORD_LOCK_INFO
WHERE TABLE_SCHEMA = 'JOBIBLOC' AND TABLE_NAME = 'CLIENTP'
RRN LOCK_STATE LOCK_STATUS JOB_NAME
9 UPDATE HELD 014827/SAKADM/SAISIECLI
9 UPDATE WAITING 014828/SAKADM/CHAINENUIT
SELECT * FROM JOBIBLOC.CLIENTP WHERE RRN(CLIENTP) = 9.
Le journal de la chaîne confirme l'étape : les étapes 1 et 2 sont terminées, la 3 a été lancée et ne s'est jamais terminée.

RUNQRY QRY(*NONE) QRYFILE((JOBIBLOC/JOURNP)) pendant le blocage.Deux minutes plus tard : MSGW et RNQ1218
Le délai d'attente d'un enregistrement est celui du fichier : le paramètre WAITRCD de
CRTPF ou CHGPF, 60 secondes par défaut, 120 pour notre
CLIENTP. Le délai écoulé, la base de données refuse la lecture
(CPF5027), le programme RPG n'intercepte pas l'erreur, et le travail passe
en MSGW :

L'option 7 (afficher le message) montre la question posée :

RNQ1218 : NUIT03ENC ne peut pas, à l'instruction 002000, allouer un enregistrement de CLIENTP.
| Réponse | Effet |
|---|---|
R | Renouveler l'opération : le programme refait le CHAIN. À choisir une fois le verrou libéré. |
C | Annuler le programme. Les clients déjà mis à jour le restent : attention à la relance. |
G | Reprendre le traitement au point *GETIN du cycle RPG. |
D | Cliché RPG formaté (dump) : les variables et les fichiers du programme, pour l'analyse. |
S | Cliché système. |
F | Cliché formaté complet. |
Avant de répondre, F10 affiche l'historique du travail. Il nomme le coupable en toutes lettres, sans avoir besoin de DSPRCDLCK :

Débloquer et relancer
L'ordre compte : d'abord libérer l'enregistrement, ensuite répondre. Répondre R tant que le verrou est tenu ne fait que relancer la lecture, qui attend de nouveau.
- Faire libérer le verrou. Le mieux est de demander à l'utilisateur de quitter son écran. Sinon, arrêter le travail qui le tient, avec ENDJOB : ses modifications non écrites sont perdues, c'est une décision à prendre avec lui.
- Répondre R au message : le programme refait le CHAIN et continue.


R au RNQ1218.
F5 : CHAINENUIT a quitté QBATCH.Le travail disparaît de QBATCH : la chaîne est allée au bout. Le journal montre les sept étapes, avec 2 min 32 s entre le lancement et la fin de l'étape 3, le temps du blocage :


Nous avons comparé les encours des clients à ceux d'une exécution sans blocage : identiques. La réponse R n'a rien perdu ni rien compté deux fois, parce qu'elle reprend la même lecture, au même endroit.
Et si l'on avait répondu C ?
Le programme NUIT03ENC aurait été annulé alors qu'il avait déjà ajouté à l'encours les commandes des clients 1 et 2. Relancer la chaîne depuis le début ajouterait ces commandes une seconde fois. Une chaîne bien conçue prévoit donc une reprise par étape (repartir de l'étape 3, et savoir lesquels de ses enregistrements sont déjà traités), ou travaille sous contrôle de validation pour annuler proprement une étape interrompue.
Et si le verrou est libéré pendant l'attente ?
Nous l'avons vérifié : verrou libéré alors que la chaîne était encore en LCKW, elle est repartie seule, sans message et sans intervention, et s'est terminée normalement. Le MSGW n'arrive que si l'attente dépasse le WAITRCD.
Éviter que la chaîne reste en MSGW
- Intercepter l'erreur dans le programme :
chain(e) cdcli CLIENTP;puisif %status(CLIENTP) = 1218;pour décider soi-même : réessayer quelques fois, écrire l'incident dans le journal, passer au client suivant ou arrêter la chaîne proprement avec un message clair. - Régler le délai :
CHGPF FILE(BIB/CLIENTP) WAITRCD(…), ouOVRDBF … WAITRCD(…)dans le CL de la chaîne pour ne changer que son attente. - Choisir le moment : une chaîne qui met à jour les clients ne devrait pas tourner pendant que les utilisateurs les modifient.
- Prévoir la reprise : un journal d'étapes comme JOURNP dit où repartir ; un pilote qui accepte une étape de départ évite de tout relancer.
- Surveiller : en SQL, QSYS2.ACTIVE_JOB_INFO filtré sur
JOB_STATUS IN ('MSGW', 'LCKW')repère les travaux arrêtés avant que quelqu'un ne s'en plaigne.
À retenir
- LCKW : le travail attend un verrou, au plus le WAITRCD du fichier (60 s par défaut).
- Pile d'appels (option 5 puis 11) : quel programme, quelle instruction.
- DSPRCDLCK ou l'historique (CPF5027) : quel travail tient l'enregistrement.
- Libérer le verrou, puis répondre R au RNQ1218 : la chaîne reprend sans perte.
- C laisse une étape à moitié faite : prévoir la reprise avant d'en avoir besoin.
Sources
Les exemples ont été créés et relevés sur un IBM i 7.5 (voir la méthode). Les définitions et les règles viennent de ces sources, consultées le 14 octobre 2026 :
- IBM i 7.3 Systems management : Work management, IBM (PDF, 212 pages). La vie d'un travail batch (SBMJOB, JOBQ, sous-système, pool mémoire, OUTQ) ; nom qualifié d'un travail à 6 chiffres ; types de travaux ; démarrage d'un travail interactif et d'un travail batch ; routage, classe et priorité d'exécution (0 à 99) ; pools *MACHINE, *BASE, *INTERACT, *SPOOL ; ordre d'une JOBQ (priorité 0 à 9, MAXACT, une file par sous-système) ; QSTRUPPGM ; OUTQ, spools, éditeurs et repli sur QGPL/QPRINT ; planning des travaux (objet QDFTJOBSCD, QJOBSCD, exemples d'ADDJOBSCDE, SBMJOB à heure fixe, reprise après arrêt).
- ACTIVE_JOB_INFO table function, IBM, documentation IBM i 7.5. Les codes de type de travail (ASJ, BCH, BCI, INT, PJ, SBS, SYS, WTR…) et d'état (RUN, DSPW, MSGW, DEQW, DLYW, LCKW…).
- ILE RPG Reference (SC09-2508-12), IBM, IBM i 7.5 (PDF). Le code d'état 01218 (enregistrement déjà verrouillé), l'extension (e) des opérations d'entrée-sortie et %STATUS ; le mot-clé BLOCK d'un fichier.
- L'aide en ligne des commandes citées, livrée avec IBM i 7.5 et reprise en français dans les fiches des commandes CL de ce site. Valeurs par défaut de SBMJOB, CRTJOBD, CRTPF (WAITRCD), CPYSPLF et DLTSPLF.
Questions fréquentes
Que veut dire LCKW dans WRKACTJOB ?
Le travail attend un verrou : un objet ou un enregistrement qu'un autre travail tient. Pour un enregistrement, l'attente dure le délai WAITRCD du fichier ; si le verrou est libéré avant, le travail repart seul, sans aucun message.
Comment savoir quel travail bloque un enregistrement ?
DSPRCDLCK FILE(BIB/FICHIER) affiche, pour chaque enregistrement verrouillé, le travail qui le tient (TENU) et ceux qui l'attendent (ATTENTE). En SQL, la vue QSYS2.RECORD_LOCK_INFO donne la même chose. L'historique du travail bloqué le dit aussi, avec le message CPF5027.
Que répondre au message RNQ1218 ?
D'abord faire libérer l'enregistrement (l'utilisateur sort de son écran, ou l'on arrête le travail qui le tient), puis répondre R : le programme refait la lecture et continue. C annule le programme : les mises à jour déjà faites restent, et une relance complète peut les faire deux fois.
Comment voir à quelle instruction un travail batch est bloqué ?
WRKACTJOB, option 5 sur le travail, puis 11 (liste d'appel des programmes) : chaque programme de la pile apparaît avec son numéro d'instruction. Dans notre démonstration, NUIT03ENC instruction 2000, c'est-à-dire le CHAIN de la ligne 20 du source.
Comment éviter qu'un batch reste en MSGW sur un verrou ?
Dans le programme, lire avec l'extension (e) et tester %status = 1218 pour décider soi-même (réessayer, journaliser, passer) ; régler le WAITRCD du fichier ; ne pas lancer la chaîne pendant que les utilisateurs modifient les mêmes données ; et prévoir une reprise par étape plutôt qu'une relance complète.