Examen 2h sur machine


Rappel : si ce n'est pas déjà le cas, vous devez configurer le workspace d'Eclipse (File > Switch WorkSpace) pour qu'il corresponde au répertoire EXAM présent dans le home de votre session de TP noté.

Vérifier que tous vos fichiers sont bien dans le répertoire EXAM. Tout ce qui n'est pas dans ce répertoire est perdu quand vous vous déconnectez.

Vous avez le droit de consulter les transparents du cours pendant tout l'examen.

La Javadoc est là : javadoc 23.

TreasureSafe

Toutes les classes demandées doivent être dans le package fr.uge.concurrence.exo1.

Dans cet exercice, on se propose de créer une classe représentant un coffre fort un peu spécial (TreasureSafe) dans lequel on peut stocker uniquement des trésors. Pour simplifier, on ne mettra dans le coffre que la valeur des trésors, donc des int. À la création de coffre, on donnera la valeur maximale (la somme des valeurs des trésors) que le coffre ne doit pas dépasser. Bien entendu, le coffre fort devra être thread-safe.

La classe TreasureSafe aura les méthodes suivantes :

Codez la classe thread-safe TreasureSafe décrite précédemment en utilisant des ReentrantLock.

Pour tester votre code, on vous fournit, dans la classe TreasureSafeMain.java, un main qui crée un coffre et démarre deux threads : un qui ajoute un trésor en boucle et l'autre qui en retire en boucle. Le programme ne doit pas s'arrêter ni se bloquer.


On veut maintenant rajouter une méthode putManyTreasures(List<Integer> values) qui va essayer de déposer plusieurs trésors dans la coffre, dans l'ordre de la liste, tant que c'est possible. Dès que tous les trésors ont pu être ajoutés, la méthode se termine. La méthode ajoute les trésors au coffre dès qu'elle le peut et attend d'avoir épuisé la liste. On pourra ainsi déposer des trésors pour une valeur totale de 1000 dans une coffre de valeur maximale 500. Autrement dit, la méthode dépose des trésors puis, si nécessaire, attend que de la place ait été faite dans le coffre pour continuer.
On veut aussi rajouter une méthode int incomingValue() qui renvoie la somme des valeur des trésors en attente d'être déposés par tous les threads qui sont en train d'exécuter une des méthodes putTreasure ou putManyTreasures.

Rajoutez les deux méthodes demandées à votre classe TreasureSafe.

Dans le main, on va en plus du code précédent (dé-commentez le code), démarrer un threads qui, en boucle appelle putManyTreasures et un thread qui en boucle affiche la valeur renvoyée par incomingValue(). Notez que pour l'instant, on ne demande à aucun thread de s'interrompre.


On veut maintenant spécifier le comportement de nos méthodes en cas d'interruption. Si les méthodes putTreasure et takeTreasure sont interrompues, elles lèvent une InterruptedException.
Si la méthode putManyTreasures est interrompue, elle ne s'arrête pas, mais elle devient prioritaire pour déposer des trésors. Tant qu'elle n'a pas fini, aucun autre thread ne peut déposer de trésor : si un thread est en train d'appeler une des méthodes putManyTreasures ou putTreasure, ces méthodes sont bloquées en attendant que cette méthode soit terminée). Si d'autres threads ont été interrompus pendant qu'ils appelaient la méthode putManyTreasures, ils sont eux aussi prioritaires et peuvent continuer à déposer des trésors. Pour signaler que le thread a été interrompu, la méthode putManyTreasures positionnera le statut d'interruption avant de terminer.

Copiez votre classe TreasureSafe dans une classe TreasureSafeWithInterruption et implanter la gestion des interruptions demandée.

Pour vous aider, voici un exemple d'affichage pour la question 2 :

deposit 14
get 14
deposit 18
get 18
deposit 10
get 10
>>> incoming 0
deposit 19
get 19
deposit 1
get 1
>>> incoming 0
Thread-1 tries to deposit [100, 50, 200, 75, 100, 25, 50, 30]
deposit 13
get 100
get 50
Thread-1 out 
get 200
deposit 10
deposit 11
>>> incoming 0
get 75
get 13
deposit 18
get 100
deposit 10
get 25
deposit 4
>>> incoming 0
get 50
deposit 4
Thread-1 tries to deposit [100, 50, 200, 75, 100, 25, 50, 30]
get 30
deposit 6
get 10
deposit 6
get 11
>>> incoming 205
deposit 8
get 18
deposit 4
get 10
deposit 3
get 4
deposit 20
>>> incoming 205
get 4
deposit 6
deposit 12
get 100
deposit 6
get 50
deposit 15
>>> incoming 80
get 200
Thread-1 out 
get 75
deposit 1
deposit 1
get 6
deposit 2
>>> incoming 0
get 6
deposit 13
get 8
deposit 19
get 4
deposit 19
>>> incoming 0
get 3
Thread-1 tries to deposit [100, 50, 200, 75, 100, 25, 50, 30]
deposit 11
deposit 7
get 20
deposit 9
get 6
deposit 2
>>> incoming 480
get 12
deposit 2
get 100
deposit 9
get 6
deposit 6
get 25
>>> incoming 480
deposit 9
get 15
deposit 1
get 50
get 30
deposit 16
get 1
deposit 6
>>> incoming 280
get 1
deposit 6
deposit 3
get 2
deposit 6
get 13
deposit 17
get 19
>>> incoming 280
deposit 19
get 19
deposit 3
get 100
deposit 18
>>> incoming 205
get 50
deposit 8
get 11
deposit 1
get 7
deposit 15
get 9
>>> incoming 205
get 2
deposit 8
get 2
deposit 16
get 9
deposit 8
>>> incoming 205
get 6
deposit 4
get 9
deposit 6
get 1
>>> incoming 205
deposit 9
get 200
Thread-1 out 
deposit 20
get 16
deposit 3
get 6
deposit 4
>>> incoming 0
get 6
deposit 3
get 3
deposit 7
get 6
get 17
deposit 15
>>> incoming 0
Thread-1 tries to deposit [100, 50, 200, 75, 100, 25, 50, 30]
deposit 12
get 19
deposit 15
get 3
deposit 11
>>> incoming 630
deposit 14
get 75
get 18
get 8
deposit 18
get 1
>>> incoming 543
get 15
deposit 13
get 8
get 16
deposit 19
>>> incoming 530
get 8
deposit 13
get 4
deposit 10
get 6
deposit 9
>>> incoming 530
get 9
get 100
deposit 17
deposit 15
get 25
>>> incoming 480
deposit 14
get 50
deposit 13
get 30
deposit 11
get 20
deposit 6
>>> incoming 480
...    

Producteur-consommateur : variante de l'API Request

Toutes les classes demandées doivent être dans le package fr.uge.concurrence.exo2.

Dans cet exercice, nous allons utiliser une variante de l'API fictive de requêtes Request.java vue en TP. Cette API très simplifiée permet de demander le prix d'un objet sur un site de vente en ligne. Pour faire cette demande, on crée une Request avec le nom de l'objet et le nom du site. La méthode request(int timeoutMilli) effectue la demande et renvoie la réponse sous la forme d'un Optional de Answer.java. La méthode bloque en attendant la réponse mais cette attente est bornée à au plus timeoutMilli millisecondes.

Request request = new Request("amazon.fr", "pikachu");
Optional<Answer> answer = request.request(5_000);
if (answer.isPresent()) {
    System.out.println("The price is " + answer.orElseThrow().price());
} else {
    System.out.println("The price could not be retrieved from the site");
}

La réponse renvoyée par request() n'est pas nécessairement fructueuse, c'est pourquoi elle renvoie un Optional<Answer>. Si le site ne dispose pas de l'item, un Optional vide est renvoyé. Sinon, une réponse est fournie et on peut récupérer le prix avec la méthode price() de l'Answer. De plus, et c'est là, la différence avec l'API d'origine, si le site ne répond pas avant le timeout donné en paramètre, une SocketTimeoutException est levée, avec comme message le nom du site qui a provoqué le timeout.

La liste (List<String>) de tous les sites connus par l'API Request peut-être obtenue par Request.getAllSites().

Dans un premier temps, l'objectif est de trouver, étant donnée une liste d'items, le site qui donne un prix pour l'ensemble des items le plus rapidement possible. Pour cela, on va utiliser un nombre fixe de threads pour chaque site, qui vont en boucle demander les prix de différents items, de façon à couvrir toute la liste.

Attention, pour simplifier l'exercice, on ne vous demande pas de gérer le cas où aucun site ne possède tous les items.

En utilisant le design pattern producteur-consommateur, compléter la classe FastestWithAllItems.java pour que la méthode retrieve demande les prix de tous les items de la liste passée en paramètre sur tous les sites et renvoie la liste des Answer du premier site pour lequel on a obtenu tous les prix. Pour chaque site, la méthode doit utiliser threadsPerSite threads pour demander les prix des items. Bien sûr, il faut faire en sorte de ne pas demander plusieurs fois le prix d'un item sur un site donné. Et c'est le thread main qui s'occupera de renvoyer la première réponse complète.
Pour l'instant, si une requête échoue parce que le timeout est dépassé, on la traite comme si l'item n'était pas disponible sur le site.
Tous les threads démarrés doivent être interrompus à la sortie de retrieve.

Attention, ici, vous devez utiliser le pattern producteur-consommateur et toute autre forme de synchronisation sera considérée comme hors-sujet. Vous n'avez pas non plus le droit d'utiliser un ExecutorService pour cette question et la suivante.

Recopier votre code de la question 1 dans un classe FastestWithAllItemsWithInterrupt.java et modifier la méthode retrieve pour que si une requête échoue sur un site parce que le timeout est dépassé, tous les threads qui effectuent des requêtes pour le même site soient également interrompus (en affichant un message à ce moment là).
Bonus : faire également en sorte que si un item n'est pas présent sur un site, tous les threads qui effectuent des requêtes pour le même site soient interrompus.

Voici le genre d'affichage que vous devriez obtenir pour la liste List.of("tortank", "pikachu", "evoli", "miaouss", "salameche") :

DEBUG : starting request for pikachu on patissier.fr
DEBUG : starting request for pikachu on leboncorner.fr
DEBUG : starting request for tortank on princedesvoleurs.com
DEBUG : starting request for tortank on ledoute.fr
DEBUG : starting request for pikachu on shady.com
DEBUG : starting request for tortank on tardy.fr
DEBUG : starting request for tortank on omazen.uk
...
DEBUG : starting request for miaouss on ledoute.fr
DEBUG : pikachu costs 426 on princedesvoleurs.com
DEBUG : pikachu costs 876 on les3alpes.fr
DEBUG : starting request for evoli on les3alpes.fr
DEBUG : starting request for evoli on princedesvoleurs.com
DEBUG : pikachu costs 830 on patissier.fr
DEBUG : starting request for miaouss on patissier.fr
DEBUG : tortank costs 218 on shady.com
DEBUG : starting request for evoli on shady.com
DEBUG : tortank costs 338 on omazen.uk
DEBUG : starting request for evoli on omazen.uk
DEBUG : pikachu costs 266 on shady.com
...
DEBUG : evoli costs 913 on fmac.fr
DEBUG : starting request for salameche on fmac.fr
DEBUG : salameche is not available on tombeducamion.fr
DEBUG : Request evoli@les3alpes.fr has timeout
Thread-18 interrupted for site les3alpes.fr
Thread-19 interrupted for site les3alpes.fr
DEBUG : miaouss costs 431 on leboncorner.fr
DEBUG : starting request for salameche on leboncorner.fr
DEBUG : Request miaouss@patissier.fr has timeout
Thread-4 interrupted for site patissier.fr
Thread-5 interrupted for site patissier.fr
DEBUG : salameche costs 246 on shady.com
DEBUG : evoli costs 111 on leboncorner.fr
DEBUG : salameche costs 992 on ledoute.fr
DEBUG : miaouss costs 521 on fmac.fr
DEBUG : salameche costs 407 on bdiscount.fr
[tortank@bdiscount.fr : 491, pikachu@bdiscount.fr : 811, miaouss@bdiscount.fr : 73, evoli@bdiscount.fr : 345, salameche@bdiscount.fr : 407]

En utilisant un ExecutorService, compléter la classe CheapestWithAllItems.java pour que la méthode retrieve demande les prix de tous les items de la liste passée en paramètre sur tous les sites et renvoie la liste des Answer d'un site pour lequel on a obtenu tous les prix pour une somme totale minimale. La méthode doit créer une tâche par site et par item pour demander les prix et elle seront effectuées par poolSize threads au total. Votre code devra en plus garantir que l'exécution de retrieve ne prend pas plus de timeoutMilliGlobal millisecondes (pour rappel, un ExecutorService possède des méthodes d'invocation de tâches avec timeout).
Si une requête échoue parce que le timeout est dépassé, le thread doit l'ignorer en affichant que le site n'as pas répondu à temps.

Aide : si answerLists est un ensemble de listes d'Answer, le code suivant donne la liste de taille nbItems de prix minimum :

answerLists.stream().filter(set -> set.size() == nbItems)
    .min(Comparator.comparingInt(set -> set.stream().mapToInt(Answer::price).sum()))
    .orElse(List.<Answer>of());

Voici le genre d'affichage que vous devriez obtenir pour la liste List.of("tortank", "pikachu", "evoli") :

DEBUG : starting request for pikachu on patissier.fr
DEBUG : starting request for evoli on patissier.fr
DEBUG : starting request for evoli on fmac.fr
DEBUG : starting request for tortank on tardy.fr
DEBUG : starting request for pikachu on fmac.fr
DEBUG : starting request for tortank on fmac.fr
DEBUG : starting request for tortank on bdiscount.fr
DEBUG : starting request for tortank on patissier.fr
DEBUG : starting request for evoli on tardy.fr
DEBUG : starting request for pikachu on tardy.fr
DEBUG : tortank is not available on patissier.fr
DEBUG : starting request for pikachu on bdiscount.fr
DEBUG : pikachu costs 830 on patissier.fr
DEBUG : starting request for evoli on bdiscount.fr
DEBUG : evoli costs 913 on fmac.fr
DEBUG : starting request for tortank on tombeducamion.fr
...
DEBUG : evoli costs 111 on leboncorner.fr
DEBUG : pikachu costs 266 on shady.com
DEBUG : pikachu costs 964 on ledoute.fr
DEBUG : Request evoli@les3alpes.fr has timeout
DEBUG : evoli costs 716 on shady.com
tombeducamion.fr did not respond in time
omazen.uk did not respond in time
les3alpes.fr did not respond in time
[tortank@tardy.fr : 372, pikachu@tardy.fr : 36, evoli@tardy.fr : 170]