Aller au contenu
Gestion des travaux · 6 / 6

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.

7programmes RPG enchaînés par un pilote
3eprogramme bloqué, sur un CHAIN de mise à jour
120 sen LCKW avant de passer en MSGW (WAITRCD)
0donnée perdue ou doublée après la réponse R

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 :

ÉtapeProgrammeRôleFichiers
1NUIT01INIOuvre la journée : compte les clients et les commandes saisies.CLIENTP, CDEL1
2NUIT02VALValide les commandes saisies (état S → V).CDEP
3NUIT03ENCAjoute le montant des commandes validées à l'encours de chaque client.CDEL1, CLIENTP en mise à jour
4NUIT04FACCrée une facture par commande validée (état V → F).CDEP, FACTP
5NUIT05STAStatistiques : encours total et plus gros client.CLIENTL1
6NUIT06PURSupprime les commandes annulées (état A).CDEP
7NUIT07FINClôture : compte les factures du jour.FACTP

Les données sont dans quatre fichiers physiques décrits en DDS et deux fichiers logiques :

CLIENTPPFClients, clé CLNUM. Créé avec WAITRCD(120) : 2 minutes d'attente sur un verrou.
CLIENTL1LFLes clients par nom.
CDEPPFCommandes, clé CDNUM, état S (saisie), V (validée), F (facturée), A (annulée).
CDEL1LFLes commandes par état, client et numéro.
FACTPPFFactures, clé FANUM.
JOURNPPFLe 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;
Pourquoi la chaîne s'arrête à l'étape 3 Le pilote NUITCHAINE, dans QBATCH, termine les étapes 1 et 2. À l'étape 3, NUIT03ENC lit le client 3 pour mise à jour, mais l'enregistrement est tenu par le travail SAISIECLI : la chaîne attend en LCKW, puis passe en MSGW sur le message RNQ1218 quand le délai WAITRCD est écoulé. Les étapes 4 à 7 ne sont pas lancées. NUITCHAINE, travail batch dans QBATCHlance les 7 étapes l'une après l'autre Étapes 1 et 2 : terminées Étape 3 : NUIT03ENC, CHAIN du client 3LCKW, puis MSGW (RNQ1218) après WAITRCD attend l'enregistrement Enregistrement 3 de CLIENTPtenu en mise à jour par le travail SAISIECLI
Les étapes 4 à 7 ne sont lancées qu'une fois l'étape 3 terminée.

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)
Écran 5250 menu principal : la commande SBMJOB de la chaîne CHAINENUIT tapée sur la ligne de commande, avec JOBD(JOBIBLOC/SIMJOBD) et INLLIBL(*JOBD)
La commande de la chaîne, tapée avant Entrée ; en bas, le message de la soumission précédente, celle de SAISIECLI. 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.
Premier essai, première panne. Sans 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.

Écran 5250 Gestion des travaux actifs : sous-système QBATCH, travail CHAINENUIT de type BCH, fonction PGM-NUITCHAINE, état LCKW
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).

Écran 5250 Liste d'appel du travail CHAINENUIT : QCMD, NUITCHAINE instruction 3300, NUIT03ENC instruction 2000, QRNXIO _QRNX_DB_CHAIN, QDBGETKY
La pile se lit de haut en bas : QCMD a appelé NUITCHAINE (instruction 3300, l'appel), qui a appelé NUIT03ENC, arrêté à l'instruction 2000, la ligne 20 du source : le CHAIN. En dessous, les routines système de la lecture par clé.

Option 12 (verrouillages du travail), puis F10 : les verrous d'enregistrements.

Écran 5250 Verrouillages d'enregistrements du travail CHAINENUIT : fichier CLIENTP de JOBIBLOC, membre CLIENTP, enregistrement 3, état ATTENTE
Le travail attend l'enregistrement n° 3 de CLIENTP.

3. Qui tient l'enregistrement ?

DSPRCDLCK sur le fichier répond directement :

Écran 5250 Verrouillages d'enregistrements du membre CLIENTP : enregistrement 3 TENU en mise à jour par SAISIECLI 014825, CHAINENUIT 014826 en ATTENTE
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
Le numéro d'enregistrement n'est pas le numéro client. C'est le numéro relatif (RRN), la place de l'enregistrement dans le membre : 3 au premier passage, 9 au second, parce que la remise à zéro a supprimé puis réinséré les clients. Pour savoir de quel client il s'agit : 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.

Écran 5250 Affichage du rapport RUNQRY sur JOURNP : début de la chaîne, étapes 1 et 2 lancées et terminées, étape 3 NUIT03ENC lancé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 :

Écran 5250 Gestion des travaux actifs : CHAINENUIT de type BCH, fonction PGM-NUITCHAINE, état MSGW
Même écran, deux minutes plus tard : MSGW, le travail attend une réponse.

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

Écran 5250 Complément d'informations sur message RNQ1218, interrogation, gravité 99 : (R C G D F) Allocation impossible d'enregistrement du fichier CLIENTP, procédure NUIT03ENC instruction 002000
RNQ1218 : NUIT03ENC ne peut pas, à l'instruction 002000, allouer un enregistrement de CLIENTP.
Écran 5250 suite du message RNQ1218 : réponses possibles R renouvellement de l'opération, D cliché RPG, S cliché système, G reprise à *GETIN, C annulation, F cliché complet
Les réponses possibles, en page suivante.
RéponseEffet
RRenouveler l'opération : le programme refait le CHAIN. À choisir une fois le verrou libéré.
CAnnuler le programme. Les clients déjà mis à jour le restent : attention à la relance.
GReprendre le traitement au point *GETIN du cycle RPG.
DCliché RPG formaté (dump) : les variables et les fichiers du programme, pour l'analyse.
SCliché système.
FCliché formaté complet.

Avant de répondre, F10 affiche l'historique du travail. Il nomme le coupable en toutes lettres, sans avoir besoin de DSPRCDLCK :

Écran 5250 Affichage de tous les messages du travail CHAINENUIT : étapes 1 à 3 lancées, Enregistrement 3 utilisé par le travail 014825/SAKADM/SAISIECLI, RNX1218 non intercepté par NUIT03ENC
Les messages du pilote (« Étape 3 : lancement de NUIT03ENC »), puis « Enregistrement 3 utilisé par le travail 014825/SAKADM/SAISIECLI ».

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.

  1. 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.
  2. Répondre R au message : le programme refait le CHAIN et continue.
Écran 5250 Gestion des travaux actifs : CHAINENUIT en MSGW, commande ENDJOB JOB(014825/SAKADM/SAISIECLI) OPTION(*IMMED) sur la ligne de commande
Le travail SAISIECLI est arrêté depuis WRKACTJOB.
Écran 5250 message RNQ1218 avec la réponse R saisie
Puis réponse R au RNQ1218.
Écran 5250 Gestion des travaux actifs : le sous-système QBATCH en DEQW, plus aucun travail CHAINENUIT
Après la réponse, 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 :

Écran 5250 rapport RUNQRY sur JOURNP : étape 3 lancée à 18.46.46, terminée à 18.49.18 avec 8 commandes ajoutées à l'encours, puis étapes 4 et 5
L'étape 3 se termine à 18.49.18 : « 8 commandes ajoutées à l'encours client ».
Écran 5250 fin du rapport JOURNP : étape 7 NUIT07FIN, clôture 8 factures émises, chaîne de nuit terminée normalement
Et la fin : « Chaîne de nuit terminée normalement ».

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; puis if %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(…), ou OVRDBF … 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 :

  1. 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).
  2. 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…).
  3. 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.
  4. 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.