Introduction
Nightjar Print Studio accepte les inscriptions à un atelier et prépare des billets téléchargeables. Une inscription peut attendre sans risque pendant l’arrêt du worker, mais le worker actuel acquitte trop tôt la livraison. Réessayer un travail terminé peut ensuite créer des billets en double.
Vous devez connaître les bases du terminal et savoir lire une petite fonction Python, l’indentation et un accès à un dictionnaire. Le court rappel ci-dessous reprend ces éléments et présente SQL et les transactions. RabbitMQ, Python, la base SQLite et le site complet sont préparés ; vous ne modifiez que handler.py. Aucune installation, compte externe, paiement ou messagerie n’est nécessaire.
Utilisez Nightjar pour les vraies pages d’inscription et de billet, et Workstation pour les commandes. Chaque unité commence indépendamment. Le site et les libellés exacts restent en anglais. Cet exercice concerne l’interruption d’un worker sur une machine ; il ne promet ni haute disponibilité ni livraison exactement une fois.
Suivre une inscription jusqu’à son billet
Dans cette étape, commencez dans Nightjar pour découvrir le fonctionnement normal du service avant de modifier le worker.
Ouvrez Explore the evening, lisez les détails puis suivez Plan your visit. Revenez avec The evening. Dans Guest name, saisissez Normal guest et choisissez Register for the evening. La nouvelle page affiche un ID, par exemple R-0002, et Your ticket is being prepared. Enregistrer une inscription ne signifie pas avoir émis son billet. Your registrations contient aussi le billet existant de Mina Patel ; préservez-le.

Le formulaire Guest name se trouve sur la page de l’atelier ; l’état d’inscription et le billet ont leurs propres pages.
Passez à Workstation. pwd affiche le répertoire courant ; cat affiche un fichier texte. Le répertoire attendu est /home/labex/project/ticket-desk :
pwd
cat handler.py
Une file conserve les messages en attente de traitement. RabbitMQ est le véritable broker ; le worker reçoit un message avec l’ID et le nom, écrit le billet et acquitte la livraison. Un acquittement indique à RabbitMQ qu’il peut retirer cette livraison. Il ne crée pas lui-même un billet.
En Python, def handle(...) définit une fonction dont les lignes indentées constituent le corps. Le worker préparé l’appelle pour chaque message. job est un dictionnaire : job['id'] et job['name'] lisent ses valeurs. ch.basic_ack(delivery.delivery_tag) acquitte cette livraison sur le canal qui l’a reçue. checkpoint est une pause pédagogique fournie : elle ne fait normalement rien ; une option --pause correspondante attend Enter ou Ctrl+C. Elle ne simule ni travail ni événement du broker.
La base contient des tables, des ensembles de lignes. db.execute(...) exécute du SQL. INSERT INTO tickets... ajoute un billet. Chaque ? reçoit une valeur du tuple suivant, afin de traiter le nom comme une donnée et non une commande SQL. with db: valide les écritures à la fin normale de son bloc indenté, ou les annule si une exception en sort. La validation les rend persistantes. Le code acquitte actuellement avant le travail ; vous corrigerez cet ordre ensuite.
python3 worker.py lance une fois le worker préparé : il reçoit un message disponible, appelle le gestionnaire et termine. Exécutez-le sans interruption :
python3 worker.py
Attendez Received R-0002 redelivered=False et Handler returned.. Des inscriptions supplémentaires peuvent changer les ID. redelivered vient de RabbitMQ, pas de la base de billets. Une exécution normale crée un vrai billet malgré l’ordre dangereux.
Revenez à Nightjar, ouvrez Your registrations, Normal guest, puis Open ticket. Choisissez Download ticket et lisez le fichier : l’ID et le nom doivent correspondre. Suivez Back to registration. Le billet de Mina Patel doit aussi s’ouvrir. Votre correction doit conserver ce parcours client.
Si le worker annonce No ready registrations, consultez la liste avant de réessayer : une exécution précédente a peut-être traité le message. En cas de faute dans le nom, inscrivez de nouveau le nom demandé ; chaque ID reste indépendant. Ne modifiez pas la base pour forcer un résultat.
Acquitter uniquement le travail terminé
Dans cette étape, commencez dans Workstation. Déplacez l’acquittement après la transaction, puis interrompez un travail incomplet pour observer son retour dans RabbitMQ.
Si le worker acquitte d’abord puis échoue, RabbitMQ n’a plus de livraison en attente à réessayer. Acquitter après la validation laisse la livraison non acquittée jusqu’à l’existence du billet. Cela protège contre l’interruption du worker ; cela ne réunit pas RabbitMQ et SQLite dans une transaction.
Remplacez uniquement handler.py avec le bloc suivant. cat > handler.py remplace le fichier ; <<'PYTHON' commence un here-document littéral. Collez tout le bloc, y compris le dernier PYTHON seul sur sa ligne. Conservez l’indentation de quatre espaces. La transaction reste identique ; seul l’acquittement passe après :
cat > handler.py <<'PYTHON'
from common import checkpoint
def handle(ch, delivery, job, db, pause):
checkpoint(pause, 'before-write')
with db:
db.execute('INSERT INTO tickets(registration_id,name) VALUES (?,?)',
(job['id'], job['name']))
checkpoint(pause, 'after-commit')
ch.basic_ack(delivery.delivery_tag)
PYTHON
Dans Nightjar, ouvrez The evening, inscrivez Retry guest et gardez sa page ouverte. Revenez à Workstation. L’option --pause before-write arrête au point situé juste avant toute écriture :
python3 worker.py --pause before-write
Attendez Paused before-write., puis pressez Ctrl+C dans le terminal. Cette interruption volontaire se termine avec le statut 130. Affichez immédiatement le statut de la commande précédente avec $? :
echo $?
Le résultat attendu est 130. Dans Nightjar, actualisez l’inscription Retry guest : aucun billet n’existe encore. Revenez à Workstation et relancez sans pause :
python3 worker.py
Attendez maintenant redelivered=True. RabbitMQ a réellement remis la livraison non acquittée en file à la fermeture de la connexion précédente. Actualisez l’inscription dans Nightjar, ouvrez le billet et téléchargez-le. Le billet récupéré appartient toujours au même ID.
Si aucune pause n’apparaît, vérifiez l’option et cat handler.py. Si vous avez pressé Enter par erreur, le worker a terminé normalement ; inscrivez un autre Retry guest et recommencez l’interruption. Si l’ancien code a acquitté avant votre interruption, python3 desk.py status affiche l’ID et python3 desk.py retry R-0003 demande une nouvelle livraison pour cette inscription ; utilisez son véritable ID. Réessayer est une action explicite de l’apprenant, jamais de l’évaluation. Ne réessayez pas de billets terminés avant d’ajouter la déduplication à l’étape suivante.
Rendre la répétition sûre pour le résultat en base
Dans cette étape, commencez dans Workstation. Une seconde fenêtre de panne subsiste : SQLite peut valider le billet, puis le worker s’arrêter avant l’acquittement RabbitMQ. Le code actuel créerait un autre billet lors de la nouvelle livraison.
L’idempotence signifie ici que retraiter le même ID laisse un seul résultat métier. Les noms ne peuvent pas identifier les inscriptions : deux personnes appelées Same name ont besoin de billets distincts. La table préparée completed possède une clé primaire id, qui interdit deux lignes de même ID.
INSERT OR IGNORE tente d’insérer cet ID. Son rowcount vaut 1 pour une nouvelle ligne et 0 si l’ID existe déjà. (job['id'],) est un tuple à une valeur ; la virgule compte. if added: n’exécute l’insertion indentée du billet que pour un nouvel ID. Les deux écritures doivent rester dans le même bloc with db:. Sinon, une panne pourrait conserver la clé sans billet, puis ignorer définitivement une inscription légitime.
Remplacez la fonction par cette petite implémentation complète. Le point de pause intérieur est volontairement placé entre les écritures pour observer l’annulation :
cat > handler.py <<'PYTHON'
from common import checkpoint
def handle(ch, delivery, job, db, pause):
checkpoint(pause, 'before-write')
with db:
added = db.execute('INSERT OR IGNORE INTO completed VALUES (?)',
(job['id'],)).rowcount
checkpoint(pause, 'inside-transaction')
if added:
db.execute('INSERT INTO tickets(registration_id,name) VALUES (?,?)',
(job['id'], job['name']))
checkpoint(pause, 'after-commit')
ch.basic_ack(delivery.delivery_tag)
PYTHON
Dans Nightjar, inscrivez Same name deux fois. Les ID sont distincts, généralement R-0004 et R-0005. Revenez à Workstation et arrêtez le premier dans sa transaction :
python3 worker.py --pause inside-transaction
À Paused inside-transaction., pressez Ctrl+C. Actualisez cette inscription dans Nightjar : aucun billet n’existe, et la clé d’achèvement ne doit pas survivre non plus. Dans Workstation, réessayez ce même ID en vous arrêtant cette fois après validation :
python3 worker.py --pause after-commit
À Paused after-commit., ouvrez l’inscription dans Nightjar : un billet existe déjà. Revenez à Workstation, pressez Ctrl+C, puis traitez à nouveau la livraison non acquittée :
python3 worker.py
Attendez redelivered=True, mais l’inscription actualisée dans Nightjar doit toujours contenir exactement un billet. L’insertion de clé en double a renvoyé 0 : le gestionnaire a ignoré le second billet tout en acquittant la livraison.
Revenez à Workstation. --drain répète la même opération à un message jusqu’à épuisement des messages disponibles :
python3 worker.py --drain
La seconde inscription Same name obtient maintenant son propre billet. desk.py status lit les véritables lignes :
python3 desk.py status
Pour tester une livraison explicitement répétée, utilisez le premier ID Same name affiché. Dans l’ordre normal, c’est R-0004 ; remplacez-le si nécessaire :
python3 desk.py retry R-0004
python3 worker.py --drain
Dans Nightjar, chaque ID Same name possède un billet téléchargeable, et Mina Patel, Normal guest et Retry guest sont préservés. Aucune inscription ne doit attendre. No ready registrations ne suffit pas : une perte de travail peut aussi laisser une file vide.
Si Python signale une erreur de syntaxe ou d’indentation, recopiez la fonction et réessayez ; le travail non acquitté revient dans RabbitMQ. Gardez les écritures ensemble. Ne supprimez pas de billets ni de clés pour masquer un problème.
Si vous avez traité un ID terminé avec l’ancien code non idempotent, corrigez d’abord handler.py. L’action fournie repair permet une récupération locale sans réinitialiser les autres participants. Trouvez l’ID avec python3 desk.py status. Si Normal guest est R-0002, prévisualisez par exemple :
python3 desk.py repair R-0002
La commande indique le plus ancien billet original à garder, les numéros supplémentaires à retirer et l’achèvement pour ce seul ID. Aucun changement n’est encore fait. --apply applique explicitement ce plan dans une transaction :
python3 desk.py repair R-0002 --apply
Utilisez l’ID concerné. L’outil refuse un ID inconnu, sans billet ou dont les noms ne correspondent pas. Il préserve le numéro et le contenu du plus ancien billet, les autres inscriptions et leurs billets. Il enregistre l’ID réparé comme terminé pour que le gestionnaire corrigé puisse l’ignorer sans risque. Il ne consomme ni n’acquitte jamais de message. Ouvrez le billet conservé dans Nightjar, puis utilisez python3 worker.py --drain pour traiter les livraisons restantes avec le code corrigé. Réparer les résultats ne fait pas réussir un gestionnaire dangereux aux contrôles isolés.
L’évaluation lit vos résultats réels et exécute votre gestionnaire inchangé contre un autre broker RabbitMQ et des bases jetables. Elle teste interruptions, redistribution réelle et annulation sans consommer, acquitter ni réparer le travail actif. Ce sont des effets de base sûrs à répéter, pas une garantie de livraison exactement une fois ; les courriels et paiements externes demandent leur propre conception.
Résumé
Vous avez récupéré les tâches de billets, distingué livraison et achèvement métier et préservé des inscriptions distinctes portant le même nom. Acquitter après validation permet de réessayer ; une clé d’achèvement et le billet dans une transaction rendent ce nouvel essai sûr pour ce résultat en base. Cela ne garantit pas une livraison exactement une fois et ne couvre ni courriels, paiements, panne du broker, ni tous les modes de panne en production.


