TP Noté 2025 : le retour de la boite à Meuh

1. Contexte et consignes

1.1. Introduction

Dans cet énoncé, nous mettons en place une architecture réseau qui s'inspire de l'anneau à jeton, en anglais token ring, qui permet le partage équitable d'une ressource sur un réseau. Tous les nœuds (node) qui souhaitent accéder à cette ressource sont placés dans un anneau où un unique jeton (token) circule de nœud en nœud. Celui qui possède le jeton possède la ressource. Il cède ensuite ce jeton au nœud suivant, le jeton tournant ainsi sur l'anneau.

Pour ceux qui l'ont suivi, le module CSC3102 de première année « Introduction aux systèmes d’exploitation » vous a déjà demandé de mettre en œuvre cette architecture lors du TP appelé « La Boite à Meuh » ; c'est donc « le retour de la Boite à Meuh ». Durant le TP de première année, les nœuds étaient des processus obligatoirement exécutés en local sur la même machine et communiquant par des tubes ; dans cet énoncé, les nœuds sont des processus pouvant être répartis sur les hôtes d'un réseau local et qui communiquent par des connexions TCP. Par ailleurs, durant le TP de première année, l'anneau était fixe : tous les nœuds de l'anneau étaient présents dès le départ ; dans cet énoncé, nous permettons l'arrivée de nouveaux nœuds dans l'anneau et à tout moment. Par contre, nous ne gérons les défaillances qu'a minima : dès que nous détectons qu'un nœud de l'anneau est défaillant (nous dirons « parti »), nous effondrons l'anneau en arrêtant tous les autres nœuds.

Mis à part le moment passager pendant lequel un nouveau nœud s'insère dans l'anneau, chaque nœud possède deux connexions ouvertes :

Pour fixer le sens de circulation du jeton, nous convenons que la première connexion établie désigne le nœud précédent, et que la connexion acceptée désigne le nœud suivant.

Comme dessiné dans la figure qui suit, l'initialisation de l'anneau est effectuée par un premier nœud qui se connecte à lui même pour former un anneau à lui tout seul.

Anneau initial avec un seul nœud
Figure 1 : Anneau initial avec un seul nœud.


Pour simplifier le sujet, nous considérons que les nœuds après le premier s'insèrent dans l'anneau en se connectant à un nœud déjà totalement inséré. Ainsi, pour insérer un nouveau nœud C entre deux nœuds A et B déjà sur présents sur l'anneau, il faut procéder au 7 étapes suivantes (notez que ces étapes restent valides pour l'anneau initial, où A et B sont alors le même nœud) :

  1. C se connecte à A (appels open par C et accept par A) ;
  2. C déclare cette connexion comme étant son lien vers son nœud précédent  ;
  3. C envoie ses InformationsDeConnexion à A pour lui indiquer l'adresse TCP qu'il occupe ;
  4. à la réception de ces InformationsDeConnexion, A envoie à son nœud suivant B un message NouvelArrivant (message transportant les informations de connexion fournies par C) ;
  5. A ferme sa connexion avec son ancien suivant B et déclare que son nouveau suivant est C ;
  6. à la réception du message NouvelArrivant, B ferme sa connexion avec son ancien précédent A, se connecte à C grâce aux informations de connexion contenues dans le message NouvelArrivant, et déclare que son nouveau précédent est C ;
  7. enfin, C accepte cette connexion et déclare cette connexion comme étant son lien vers son suivant.

Un nouveau nœud C s'insère entre deux nœuds de l'anneau (A et B)
Figure 2 : Ajout d'un nouveau nœud à l'anneau.

En plus de la gestion des arrivées de nouveaux nœuds, l'anneau fait circuler de nœud en nœud un jeton, qui est créé par le tout premier nœud de l'anneau.




1.2 Consignes

Vous disposer de 3h pour réaliser ce TP. Vous avez le droit aux documents suivants (liste exhaustive) :

Pour installer le sujet :

En fin de TP :

  1. si ce n'est pas déjà fait, ajoutez les informations à destination des correcteurs dans le fichier readme.md ;
  2. supprimez tous les fichiers générés avec un « mvn clean » ;
  3. faites une archive zip ou tgz :
  4. déposez le fichier sur Moodle comme devoir ;
  5. avant de quitter la salle, vérifiez avec l'enseignant présent dans la salle que le fichier téléchargé sur Moodle est correct. L'enseignant charge votre projet dans Eclipse pour faire cette vérification.

2. Présentation du sujet

2.1. Architecture et messages

Le programme à écrire se compose de quatre classes et une énumération, toutes présentes dans le package tsp.csc4509.tpnote :

Vous n'avez pas besoin d'aller lire les autres classes présentes dans ce projet pour comprendre et écrire ce TP.

3. Les questions du sujet

3.1. Introduction

L'énoncé comporte 9 questions, toutes dans la classe Node.

Le barème est donné à titre d'indication, et pourra évoluer lors de la correction des copies.

Attention : Le symptôme d'une défaillance dans l'anneau est la détection d'une connexion TCP/IP perdue. Cette détection se fait donc en constatant une fermeture de connexion non prévue par l'algorithme. Il faut donc absolument que toutes les déconnexions prévues par l'algorithme soit totalement gérées : fermeture du FullDuplexMsgWorker concerné, retrait de ce FullDuplexMsgWorker des Map où il est présent, et désinscription auprès des Selector où il est enregistré.

Des tests unitaires sont fournis pour les questions 1—6. Des tests d'intégration sont fournis pour les questions 7—9. Suivez les consignes qui vous sont données pour chacune de ses questions pour les utiliser.

3.2. Écriture de la classe Node

3.2.1. Introduction

La classe Node, qui est dans le paquetage tsp.csc4509.tpnote, réalise tout l'algorithme qui gère l'anneau. Elle possède 5 attributs :

Pour rappel : un émetteur d'un message peut très bien faire ses envois en mode synchrone, tandis que le récepteur en face gère les lectures en mode asynchrone.

Les deux constructeurs et les cinq méthodes de la classe restent à écrire. C'est le sujet des sept premières questions. Vous pouvez décider d'écrire la réponse à une question quand vous le voulez, mais vous devez retenir que :

3.2.2. Question 1 : écriture de Node::initNode() (2,5 points)

La classe Node utilise deux constructeurs. Une grande partie de leur code est commun. La méthode Node::initNode() définit ce code commun.

Cette méthode possède quatre paramètres :

  1. String id : l'identifiant du Node à construire ;
  2. int monPort : le port occupé par le Node ;
  3. String sonHost : le nom de la machine sur laquelle s'exécute le Node auquel ce Node doit se connecter ;
  4. int sonPort : le port occupé par le Node auquel ce Node doit se connecter.

Cette méthode initialise les attributs de la classe et connecte le nouveau Node en construction. Pour ce faire, elle doit :

  1. ouvrir le Selector déclaré en attribut de la classe ;
  2. créer la collection connexions déclarée en attribut de la classe ;
  3. initialiser l'attribut id avec le paramètre id de la méthode ;
  4. ouvrir le listenChannel déclaré en attribut de la classe en occupant le numéro de port contenu dans le paramètre monPort. À ce stade du sujet, ce canal reste en synchrone ;
  5. ouvrir une connexion asynchrone sur l'adresse TCP/IP sonHost:sonPort, l'enregistrer en lecture sur le Selector de la classe, créer un FullDuplexMsgWorker à partir de ce canal, ajouter ce worker dans la collection connexions et retourner ce worker en résultat.

La connexion ainsi ouverte est le lien vers le Node précédent de l'anneau.

Écrivez la méthode initNode().

Les tests :

Une fois cette méthode écrite, placez en commentaire l'annotation @Disabled du test testInitNode() de la classe de tests TestNode. Exécutez ce test et corrigez les éventuelles erreurs.

Le niveau d'affichage des logs se fixe dans la méthode initTest de la classe de test, lignes 76 à 78. Ils sont actuellement réglés au niveau WARN.

Si vous n'arrivez pas à corriger tous les problèmes, vous pouvez quand même passer aux questions suivantes, mais les tests des quesions 2 et 3 ne pourront pas passer.

3.2.3. Question 2 : écriture du premier constructeur de Node (2 points)

Le premier constructeur de Node sert à construire le premier Node de l'anneau, celui qui se connecte à lui-même pour former à lui seul l'anneau initial (cf. la figure 1 dans l'introduction du sujet).

Ce constructeur possède deux paramètres :

  1. String id : l'identifiant du Node à construire ;
  2. int port : le port occupé par le Node.

Ce constructeur doit :

  1. réaliser toute la partie commune des constructeurs en utilisant la méthode écrite à la question 1 (initNode) pour initialiser les attributs et se connecter à lui-même (son adresse TCP/IP vaut localhost:port) ;
  2. accepter la connexion qu'il vient de demander (par l'appel précédent à initNode), et initialiser l'attribut workerSuivant avec cette connexion (résultat de accept) ;
  3. passer en mode asynchrone le canal listenChannel (attribut de la classe) ouvert lors de la question 1, et l'enregistrer au auprès du selector (attribut de classe) dans la catégorie des accept ;
  4. Générer le jeton de l'anneau (new Token(id)), et l'envoyer sur le workerSuivant qu'il vient de créer.

Donc, la partie spécifique de ce constructeur crée le lien avec le Node suivant (lui-même), et initialise l'anneau en injectant le jeton dans les échanges.

Écrivez ce premier constructeur.

Les tests :

Une fois cette méthode écrite, placez en commentaire l'annotation @Disabled du test testContructeur1() de la classe de tests TestNode. Exécutez ce test, et corrigez les éventuelles erreurs.

Si vous n'arrivez pas à corriger tous les problèmes, vous pouvez passer aux questions suivantes, mais vous ne pourrez pas passer les tests d'intégration de la quesion 7.

3.2.4. Question 3 : écriture du second constructeur de Node (1,5 point)

Le second constructeur de Node sert à construire un Node qui rejoint un anneau déjà initialisé. Il réalise toutes les actions du nouveau Node C dans la figure 2 de l'introduction du sujet. Dans la liste des actions à réaliser par ce constructeur, nous utilisons les numéros des étapes pour décrire l'avancement de la construction. Mais attention, ce code ne concerne que le point de vue du nouveau Node en construction. Les actions des Nodes déjà présents dans l'anneau (A et B) seront écrites dans d'autres méthodes.

Ce constructeur possède quatre paramètres :

  1. String id : l'identifiant du Node à construire ;
  2. int monPort : le port occupé par le Node ;
  3. String sonHost : le nom de la machine sur laquelle s'exécute le Node auquel ce Node doit se connecter ;
  4. int sonPort : le port occupé par le Node auquel ce Node doit se connecter.

Ce constructeur doit :

  1. (étape 1 et 2 de la figure 2) réaliser toute la partie commune des constructeurs en utilisant la méthode écrite à la question 1 (initNode) pour initialiser les attributs et se connecter à un Node déjà présent dans l'anneau (A) à l'adresse TCP/IP sonHost:sonPort. Le FullDuplexMsgWorker, résultat de cette méthode, doit être mémorisé dans une référence, car il est utilisé pour envoyer un message au Node auquel on vient de se connecter ;
  2. (étape 3 de la figure 2) construire une instance de InformationsDeConnexion contenant l'adresse TCP/IP occupée par le Node en construction :

    et ensuite il faut envoyer ces InformationsDeConnexion au Node auquel on vient de se connecter (A), donc en utilsant le FullDuplexMsgWorker résultat de initNode() ;
  3. (étape 7 de la figure 2) accepter la connexion du Node suivant (B) et donc initialiser l'attribut workerSuivant ;
  4. passer en mode asynchrone le canal listenChannel (attribut de classe) ouvert lors de la question 1, et l'enregistrer au auprès du selector (attribut de classe) dans la catégorie des accept.

Donc, la partie spécifique de ce constructeur envoie les InformationsDeConnexion au Node précédent (A), et crée le lien avec le Node suivant (B).

Écrivez ce second constructeur.

Les tests :

Une fois cette méthode écrite, placez en commentaire l'annotation @Disabled du test testContructeur2() de la classe de tests TestNode. Exécutez ce test, et corrigez les éventuelles erreurs.

Si vous n'arrivez pas à corriger tous les problèmes, vous pouvez passer aux questions suivantes, mais vous ne pourrez pas passer les tests d'intégration de la quesion 7.

3.2.5. Question 4 : écriture la méthode Node::traiterJeton() (1,5 point)

Cette méthode est appelée lorsque le Node reçoit le jeton. Elle marque le jeton avec l'identifiant id du Node (pour faciliter les tests, le jeton transporte son historique d'usage), simule une tâche longue (en s'endormant), et transmet le jeton sur l'anneau à son Node suivant.

Cette méthode possède un paramètre :

  1. Token token : le jeton que le Node vient de recevoir.

Attention : Vous noterez que le prototype de cette méthode indique ne lever aucune exception. À terme, cette méthode est appelée dans la méthode run() d'un Runnable, qui ne peut pas lever d'exception. C'est pourquoi vous devez attraper et traiter toutes les exceptions qui peuvent se produire dans le traitement du jeton. Une exception dans cette méthode signifie la perte du jeton. L'anneau n'est alors donc plus fonctionnel. Pour effondrer cet anneau, le traitement des exceptions consiste à fermer le workerSuivant (attribut de la classe). Le code de la question 7 demande de détecter cette fermeture pour que chaque Node de l'anneau propage cet effondrement en cascade.


Cette méthode doit :

  1. marquer le jeton pour placer le Node dans son historique d'usage :
  2. simuler une tâche longue en s'endormant :
  3. transmettre le jeton au Node suivant de l'anneau (donc via l'attribut workerSuivant de la classe).

Écrivez la méthode traiterJeton().

Les tests :

Une fois cette méthode écrite, placez en commentaire l'annotation @Disabled du test testTraiterJeton() de la classe de tests TestNode. Exécutez ce test, et corrigez les éventuelles erreurs.

Si vous n'arrivez pas à corriger tous les problèmes, vous pouvez passer aux questions suivantes, mais vous ne pourrez pas passer les tests d'intégration de la quesion 7.

3.2.6. Question 5 : écriture la méthode Node::changerSuivant()(2 points)

Cette méthode est appelée lorsqu'un Node déjà présent dans l'anneau a accepté la demande de connexion d'un nouveau Node et a totalement reçu les InformationsDeConnexion de celui-ci. Elle réalise les actions du Node A de la figure 2 de l'introduction du sujet, après l'étape 3. Dans la liste des actions à réaliser par cette méthode, nous utilisons les numéros des étapes pour décrire son avancement. Mais attention, ce code ne concerne que le point de vue du Node A. Les actions des Node B et C sont écrites dans d'autres méthodes.

Cette méthode possède trois paramètres :

  1. InformationsDeConnexion infosConnexion : le message envoyé par le Node qui a été accepté, et contenant l'adresse TCP/IP de celui-ci ;
  2. FullDuplexMsgWorker nouveauSuivant : le worker lié au nouveau Node (C) qui a été accepté (et qui doit devenir le nouveau suivant pour insérer C dans l'anneau) ;
  3. SelectionKey key : la clef d'enregistrement du nouveauSuivant (connecté à A) auprès du selector (attribut de la classe).

Cette méthode décrit les actions du Node A à partir de l'étape 3. Mais, lors de l'étape 1 (traitée dans la boucle de la question 7), ce Node a accepté la connexion du Node C, et a passé cette connexion en asynchrone pour préparer la réception du message infosConnexion que nous traitons dans cette question. À partir de maintenant, le Node A ne fait plus aucune lecture sur cette connexion. Donc, la première chose à faire dans cette méthode est de repasser cette connexion en mode synchrone.

Cette méthode doit :

  1. repasser en synchrone la connexion associée à nouveauSuivant. Il faut donc :
  2. construire une instance de NouvelArrivant à partir des InformationsDeConnexion :
  3. (étape 4) envoyer ce NouvelArrivant au Node suivant (B) ;
  4. (étape 5) fermer la connexion avec ce Node suivant (B), et prendre pour nouveau Node suivant celui qui vient d'être accepté (C).

Écrivez la méthode changerSuivant().

Les tests :

Une fois cette méthode écrite, placez en commentaire l'annotation @Disabled du test changerSuivant() de la classe de tests TestNode. Passez ce test, et corrigez les éventuelles erreurs.

Si vous n'arrivez pas à corriger tous les problèmes, vous pouvez passer aux questions suivantes, mais vous ne pourrez pas passer les tests d'intégration de la quesion 7.

3.2.7. Question 6 : écriture la méthode Node::changerPrecedent() (2 points)

Cette méthode est appelée lorsqu'un Node déjà présent dans l'anneau reçoit de son Node précédent le message NouvelArrivant. Elle réalise les actions du Node B de la figure 2 de l'introduction du sujet, lors de l'étape 5.

Cette méthode possède trois paramètres :

  1. NouvelArrivant nouvelArrivant : le message envoyé par le Node précédent (A) pour lui signaler l'arrivée d'un nouveau Node (C) à insérer dans l'anneau ;
  2. FullDuplexMsgWorker workerPrecedent : le worker connecté au Node précédent (A) avant l'insertion du nouveau Node ;
  3. SelectionKey key : la clef d'enregistrement du workerPrecedent (connecté à A) auprès du selector (attribut de la classe).

Cette méthode doit :

  1. fermer la connexion avec son Node précédent (A) ;
  2. retirer ce worker de la collection des connexions ;
  3. retirer la clef de ce worker des clefs inscrites dans le selector ;
  4. extraire du message NouvelArrivant l'adresse TCP/IP du nouveau Node (C) :
  5. établir une connexion asynchrone avec ce nouveau Node (C) ;
  6. enregistrer cette nouvelle connexion auprès du selector en mode lecture, et l'ajouter à la collection des connexions.

Écrivez la méthode changerPrecedent().

Les tests :

Une fois cette méthode écrite, placez en commentaire l'annotation @Disabled du test changerPrecedent() de la classe de tests TestNode. Exécutez ce test, et corrigez les éventuelles erreurs.

Si vous n'arrivez pas à corriger tous les problèmes, vous pouvez passer à la question suivante, mais vous ne pourrez pas passer les tests d'intégration de la quesion 7.


3.2.8. Question 7 : écriture la méthode Node::boucler() (5,5 points)

Cette méthode gère tous les échanges asynchrones du Node : les nouvelles connexions, les déconnexions et les réceptions de messages du Node précédent. Il s'agit donc de la boucle classique avec un Selector.

Cette méthode doit boucler tant que l'anneau ne s'effondre pas. Si la moindre lecture de cette boucle se termine à cause d'une déconnexion avant que le message ne soit complet, vous devez considérer que l'anneau s'effondre.

Vous devez écrire une boucle (pas infinie) qui doit :

Écrivez la méthode boucler().

Les tests :

Pour réaliser les tests d'intégration, nous ajoutons dans le code du Node une sonde qui envoie à un programme de log toutes les évolutions de l'état de chaque Node. Pour ce faire, il faut ajouter dans votre code, dans la boucle et juste avant l'instruction selector.select() la ligne suivante :

Cette même sonde est déjà présente dans le code de la classe Token. Ainsi, le programme de log enregistre chaque modification de l'anneau ou du jeton. Les tests d'intégration consistent à comparer le log généré par votre code avec un log de référence.

Pour voir vos propres logs durant ces tests, vous pouvez changer leurs niveaux d'affichage. Les niveaux d'affichage des logs se fixent dans la classe tsp.csc4509.appli.AppliNode lignes 72 et 73. Ils sont actuellement réglés au niveau WARN.

Pour passer les tests, lancez depuis la racine du projet (le répertoire où il y a le fichier pom.xml) les scripts suivants :

Les tests de terminent par la ligne « Test OK » lorsqu'ils passent.

Sinon, vous pouvez exploiter le log généré par DebugTools pour comprendre l'erreur. Pour cela, il faut comprendre le fonctionnement de ces tests.

Les tests d'intégration :

Si le log correspond au fichier de référence, le test réussit, sinon le test échoue. Si votre code contient des affichages de logs, ils sont affichés durant tout le déroulement. Mais, dans tous les cas, le test termine son affichage par un résumé et son statut (échec, ou réussi). Voici, par exemple, la fin d'un test qui échoue :

Les fichiers de log ressemblent à :

La différence à noter entre les deux fichiers est l'absence de toute activité sur l'anneau après le second usage du jeton. Pour information, le code erroné pense bien à générer le jeton (premier usage), pense bien à le recevoir une première fois (second usage), mais oublie de le reposter sur l'anneau (fin de l'activité de l'anneau).

Une nouvelle ligne de log est générée à chaque événement sur un Node (connexion, déconnexion, ou réception d'un message) et à chaque modification du jeton.

Les événements sur les Nodes génèrent une ligne débutant par « Ring » qui affiche l'anneau avec les Nodes dans l'ordre alphabétique (pas dans l'ordre de l'anneau). Exemple de ligne Ring :

Sur cet anneau, le Node B est entre C et A, donc cette ligne décrit l'anneau "A -> C -> B -> A".

Les modifications du jetons génèrent une ligne débutant par « Token ». Elle affiche le nombre total d'usages, l'identifiant du Node qui en a l'usage ainsi que la liste des derniers utilisateurs : en tête de liste, le plus ancien usage et en queue de liste le Node qui en a l'usage actuellement. Cette liste ne conserve que les 10 derniers usages. Exemple de ligne Token :

Ce jeton a été utilisé 10 fois. Au début, seul le Node A était présent dans l'anneau. Il était donc le seul à utiliser le jeton. Ensuite les Node B et C sont entrés dans l'anneau. Enfin, le jeton est actuellement possédé par C qui l'a reçu de A.

Lorsqu'un nouveau Node s'insère dans l'anneau, il y a une période transitoire pendant laquelle certains liens sont rompus. Dans ce cas, le Node suivant ou précédent est indiqué par la mention « AUCUN ». L'activité de l'anneau peut cependant continuer. Tant que le jeton est utilisé par un Node qui a encore un lien vers un Node suivant, il peut circuler. Dans le scénario ci-dessous, un nouveau Node C s'insère entre B et A, mais à aucun moment le lien allant de A vers B n'est rompu. Donc, A qui possédait le jeton a pu le poster à B :

3.2.9. Question 8 : placer l'appel de Node::traiterJeton() dans un Thread (1,5 point)

Si vous avez réalisé les tests d'intégration de la question 7, vous avez dû constater que l'insertion d'un nouveau Node dans l'anneau peut parfois prendre beaucoup de temps. En effet, lorsqu'un Node possède le jeton, il est occupé par ses activités (dans notre code, une pause de 5 secondes placée dans traiterJeton), et ne fait rien d'autre. Donc, si un nouveau Node (C) veut s'insérer dans l'anneau et que le Node ayant le jeton est concerné (que ce soit A ou B), l'insertion ne peut pas se terminer avant la fin de traiterJeton().

En déportant l'appel de la méthode traiterJeton() dans un nouveau thread nous résolvons ce problème.

Modifiez le code de la méthode boucler() pour que l'appel de la méthode traiterJeton() soit placé dans un nouveau Thread. Privilégiez la solution avec une expression lambda. Mais, si vous ne savais pas l'écrire, vous pouvez utiliser une classe anonyme ou écrire complètement la classe qui met en œuvre le Runnable.

Les tests :

Pour passer les tests, lancez depuis la racine du projet (le répertoire où il y a le fichier pom.xml), le script ./TestInteg5.sh

Il s'agit exactement du même scénario que le TestInteg3.sh : création de l'anneau avec le NodeA, connexion du NodeB sur A, et puis ensuite connexion du NodeC sur A. Mais, le log est différent. L'insertion des deux nouveaux Nodes est totalement finie avant que le Node A ait fini le traitement du jeton.

Notez qu'avec ces modifications les log de référence des tests 1 à 4 ne sont plus correctes, et qu'il est donc normal que dorénavant ces 4 tests échouent.

3.2.10. Question 9 : gestion des exclusions mutuelles (1,5 point)

Une fois l'appel de la méthode traiterJeton() placé dans un thread, workerSuivant est utilisé dans deux threads différents. Il est aisé d'imaginer des scénarios où cette situation provoquerait des problèmes. En voici par exemple un :

Pour vérifier que les corrections apportées au code réglent bien ce problème, nous ajoutons une modification pour rendre le risque de conflit certain s'il n'est pas protégé. Pour cela, ajoutez dans la méthode changerSuivant les 4 lignes suivantes juste après l'envoi du message NouvelArrivant :

Ensuite, exécutez le test ./TestSectionCritique.sh

À ce stade, ce test doit échouer. Il doit réussir après l'écriture des corrections demandées par cette question.

Ce test réalise le scénario décrit plus haut, mais sur un anneau minimal : il insère un Node B sur un anneau ne contenant qu'un seul Node A. Mais la situation est bien celle décrite : le Node A fait tourner deux threads en même temps, l'un exécutant traiterJeton et l'autre exécutant changerPrecedent. Le log de ce test, exécuté dans l'état actuel du code, montre qu'il n'y a plus trace du jeton à partir du moment où le Node B s'insère dans l'anneau. Ce jeton a été perdu.

Sachant qu'une fois le Node construit, l'attribut workerSuivant n'est utilisé que dans deux méthodes : traiterJeton et changerPrecedent, identifiez les sections critiques à placer en exclusion mutuelle, et utilisez les blocs synchronisés pour les protéger. Vous veillerez à ne bloquer la ressource que le temps nécessaire.

Les tests :

Pour passer les tests, lancez depuis la racine du projet (le répertoire où il y a le fichier pom.xml), le script ./TestSectionCritique.sh

Il doit maintenant réussir.

Une fois cette vérification faite, vous pouvez retirer le Thread.sleep(6000) de la méthode changerSuivant. Mais une fois cette méthode nettoyée de ces lignes, le test ./TestSectionCritique.sh échoue de nouveau, non pas parce que le code est bugué, mais parce que l'anneau insère le Node beaucoup plus rapidement et le log de référence n'est plus conforme.

4. Conclusion

Ce TP noté est terminé et vous avez écrit une première version opérationnelle du code permettant de mettre en œuvre la gestion d'un Token Ring.

Félicitations !