Accueil / Plusieurs agents et vérification adverse
Réussi, échoué, interrompu, sans trace
Quand une coupure arrête une file de tâches, un journal tenu hors du processus, écrit avant et après chaque tâche, sépare quatre états : réussi, échoué, interrompu en cours de route, et sans trace de tentative ; les confondre fait relancer ce qui a peut-être déjà agi, ou déclarer en échec ce que personne n'a examiné.
Prenons un cas type. Une chaîne de vérifications traite une file de tâches l'une après l'autre. Un plafond d'usage tombe au milieu de la file et arrête le processus superviseur avec les agents qu'il pilotait. Au redémarrage, le superviseur marque en échec toutes les tâches sans résultat, y compris celles qu'aucun agent n'avait commencées.
Quatre états, parce qu'une coupure en crée deux
Un système qui ne connaît que réussi et échoué range dans échoué deux cas que la coupure produit. La tâche interrompue a commencé : elle a pu agir en partie, et elle s'inspecte avant d'être relancée. La tâche sans trace se relance, une fois vérifié qu'aucun autre traitement ne l'a prise. Écrire la ligne « en cours » avant de commencer est ce qui les sépare : sans elle, une tâche interrompue ressemble à une tâche jamais abordée.
L'absence de ligne établit une chose bornée, comme le rappelle la leçon sur ce qu'une vérification rigoureuse ne voit pas : ce journal ne porte aucune trace de tentative, et il ne dit rien d'un autre processus qui aurait pu prendre la tâche.
La démonstration
Les deux scripts se créent dans un dossier jetable. Le premier écrit six tâches dans file.json, puis simule une coupure pendant la quatrième. Le second confronte cette liste de départ au journal : c'est elle qui fait connaître les tâches sans trace.
mkdir -p demo-file && cd demo-file
cat > traiter-file.js << 'EOF'
// Fabrique la file de départ, puis la traite en journalisant avant et après chaque tâche.
const fs = require('fs');
const file = ['tâche-1', 'tâche-2', 'tâche-3', 'tâche-4', 'tâche-5', 'tâche-6'];
fs.writeFileSync('file.json', JSON.stringify(file));
fs.writeFileSync('journal.jsonl', '');
const resultats = { 'tâche-1': 'réussi', 'tâche-2': 'échoué', 'tâche-3': 'réussi' };
for (const id of file) {
fs.appendFileSync('journal.jsonl', JSON.stringify({ id, 'état': 'en cours' }) + '\n');
if (id === 'tâche-4') process.exit(1); // coupure simulée pendant la quatrième tâche
fs.appendFileSync('journal.jsonl', JSON.stringify({ id, 'état': resultats[id] }) + '\n');
}
EOF
cat > lire-journal.js << 'EOF'
// Relit la file de départ et le journal, et classe chaque tâche en quatre états.
const fs = require('fs');
const file = JSON.parse(fs.readFileSync('file.json', 'utf8'));
const dernier = {};
for (const ligne of fs.readFileSync('journal.jsonl', 'utf8').split('\n')) {
if (ligne) { const e = JSON.parse(ligne); dernier[e.id] = e['état']; }
}
const classes = { 'réussi': [], 'échoué': [], 'interrompu': [], 'sans trace': [] };
for (const id of file) {
const etat = dernier[id];
if (etat === undefined) classes['sans trace'].push(id);
else if (etat === 'en cours') classes['interrompu'].push(id);
else classes[etat].push(id);
}
for (const [nom, ids] of Object.entries(classes)) console.log(nom + ' : ' + (ids.join(', ') || '-'));
EOF
node traiter-file.js
echo "code de sortie : $?"
cat journal.jsonl
node lire-journal.js
code de sortie : 1
{"id":"tâche-1","état":"en cours"}
{"id":"tâche-1","état":"réussi"}
{"id":"tâche-2","état":"en cours"}
{"id":"tâche-2","état":"échoué"}
{"id":"tâche-3","état":"en cours"}
{"id":"tâche-3","état":"réussi"}
{"id":"tâche-4","état":"en cours"}
réussi : tâche-1, tâche-3
échoué : tâche-2
interrompu : tâche-4
sans trace : tâche-5, tâche-6
Le code de sortie 1 signale l'arrêt anormal. La tâche 4 porte « en cours » sans résultat : interrompue, pas échouée.
Une trace qui survit au processus
Un bilan gardé dans la mémoire du superviseur disparaît avec lui. Le journal vit donc hors du processus : un fichier comme ici, une table de base de données ou une file de messages externe conviennent aussi. appendFileSync termine l'écriture de la ligne avant de passer à la suite : si le processus est tué, les lignes déjà écrites restent dans le fichier. Une coupure de courant est un autre cas, parce que le système peut garder les dernières lignes en mémoire avant de les poser sur le disque ; une synchronisation explicite, fs.fsyncSync en Node, force cette écriture.
La leçon sur les trois états d'échec sépare, parmi les tâches qui ont rendu un résultat, l'échec récupérable qui sera retenté et l'échec définitif qui remonte à une personne ; celle-ci traite les tâches que la coupure a laissées sans résultat.
Ce que le journal enregistre, tâche par tâche
Quatre états, quatre gestes
| État | Trace dans le journal | Geste suivant |
|---|---|---|
| Réussi | « en cours » puis « réussi » | Aucun |
| Échoué | « en cours » puis « échoué » | Traiter selon le type d'échec |
| Interrompu | « en cours » et rien après | Inspecter les effets partiels, puis relancer |
| Sans trace | Aucune ligne, tâche présente dans la liste de départ | Vérifier qu'aucun autre traitement ne l'a prise, puis relancer |
Un traitement parcourt une file de six tâches listées dans un fichier de départ. Il écrit dans un journal sur disque une ligne « en cours » avant chaque tâche et une ligne de résultat après. Un plafond d'usage arrête le processus. Relu après l'arrêt, le journal contient pour la tâche 1 « en cours » puis « réussi », pour la tâche 2 « en cours » puis « échoué », pour la tâche 3 « en cours » puis « réussi », et pour la tâche 4 « en cours ». Les tâches 5 et 6 figurent dans le fichier de départ.
Écrivez en une phrase ce que cette situation établit, et en une phrase ce qu'elle n'établit pas.
Ce que cela établit : Elle établit que les tâches 1 et 3 ont réussi, que la tâche 2 a échoué, que la tâche 4 a commencé et n'a pas de résultat enregistré, et que le journal ne porte aucune trace de tentative pour les tâches 5 et 6.
Ce que cela n’établit pas : Elle n'établit pas que la tâche 4 est restée sans effet, puisqu'elle a pu agir en partie avant l'arrêt, ni qu'aucun autre processus n'a traité les tâches 5 et 6 : le journal ne décrit que ce traitement.
Les trois calibrages faux les plus courants
- Trop large Les tâches 4, 5 et 6 ont échoué à cause du plafond d'usage et se relancent toutes les trois de la même façon.
- Trop étroit Le journal établit l'état des tâches 1 à 3 ; pour les tâches 4, 5 et 6, il ne permet de rien distinguer.
- À côté La situation établit que le plafond d'usage était réglé trop bas pour la taille de cette file.
- Une tâche interrompue a commencé et a pu agir en partie, elle s'inspecte donc avant d'être relancée.
- Une tâche sans trace se reconnaît en confrontant la liste de départ au journal, et non au journal seul.
- La ligne écrite avant de commencer est ce qui sépare une tâche interrompue d'une tâche que le traitement n'a pas abordée.
- Un journal utile vit hors du processus qui l'écrit : fichier, base de données ou file de messages externe.
Prenez un script qui traite une liste d'éléments, faites-lui écrire une ligne « en cours » avant chaque élément et une ligne de résultat après, arrêtez-le à la main en plein milieu, puis vérifiez que vous retrouvez les quatre états en confrontant sa liste de départ au journal.