• Aucun résultat trouvé

La logique du protocole est implémentée dans deux modules C qui correspondent au com-portement générique d’une porte et d’un manager. Ces deux modules sont la traduction en C des spécifications LNT présentées dans la section 2.7. Ils comportent en plus la ges-tion des offres et les appels aux différentes foncges-tions crochets (si elles sont définies par l’utilisateur).

Cette implémentation est réalisée une fois pour toutes, les deux modules génériques étant ensuite repris au sein de l’implémentation générée automatiquement pour une spécifica-tion LNT de système distribué. Cette isolaspécifica-tion de l’implémentaspécifica-tion du cœur de la logique du protocole permet de gagner en confiance en l’absence de bogues, par rapport à une implémentation du protocole qui serait générée à chaque fois.

Étant donné que nous avons spécifié et, comme nous le verrons au chapitre 5, vérifié le comportement d’une porte et d’un manager en LNT, il semble pertinent d’utiliser le compilateur EXEC/CÆSAR pour obtenir un programme C qui implémente les processus porte et manager. Cette approche d’amorce (bootstrap) à partir de la spécification LNT est attirante, mais se révèle complexe en pratique : il faut non seulement gérer les offres, mais aussi s’interfacer avec la bibliothèque CÆSAR_NETWORK pour les communications distantes. De plus, la porte et la partie manager doivent appeler les fonctions crochets qui peuvent contenir des effets de bord, or nous avons vu en section 3.1.1 qu’il est difficile de placer des effets de bord au sein d’une spécification LNT autre part que sur une action.

4.3. Implémentation du protocole 103 Cependant, l’amorce de l’implémentation du protocole de synchronisation à partir de sa spécification, qui est une étape clé du bootstrappingde DLC à partir de LNT, est une piste de travail futur.

Nous avons donc choisi d’écrire directement à la main la logique du protocole en C pour les raisons suivantes :

— cette implémentation manuelle est un effort à réaliser une seule fois

— il est de toute manière nécessaire d’écrire une partie de l’implémentation du pro-tocole en C, ne serait-ce que pour avoir accès aux primitives de communication à distance

— le protocole de synchronisation est un élément crucial de la performance de l’im-plémentation générée, et l’écrire directement en C offre un meilleur contrôle sur les performances en temps d’exécution mais aussi en gestion de la mémoire

Remarque 4-1

La génération d’implémentation distribuée pour BIP [Qui13] utilise une approche qui met en place le protocole de synchronisation au niveau de la spécification BIP. Une spécification BIP est transformée par l’injection de la logique du protocole de synchronisation, pour obtenir une nouvelle spécification BIP où les interactions entre processus se limitent à du passage de message entre deux processus uniquement. Ensuite, cette spécification est traduite vers du code exécutable, où les échanges de messages entre deux processus peuvent être aiséments transcrits sur des primitives bas niveau de passage de messages.

Cette approche vise à être “correcte par construction” en démontrant formellement l’valence entre le modèle BIP avant et après insertion du protocole. Cependant, cette équi-valence est vérifiée pour un protocole de synchronisation naïf, mais pas pour le protocole

α-core (corrigé) véritablement utilisé dans le générateur de code.

4.3.1 Choix non déterministe sur la réception de message

Dans la spécification LNT des processus porte et manager, la réception de messages du protocole se fait par une action sur la porte RECV. Le flot d’exécution du processus est aiguillé selon le type de message reçu grâce à un choix non déterministe entre plusieurs actions sur la porte RECV avec différents types de messages en offre. De plus, un processus peut refuser la réception de certains message selon son état courant. Par exemple, une porte accepte toujours de recevoir un message “ready”, mais accepte un message “commit” uniquement si elle est en train de négocier :

−−Boucle principale du processus porte

loop select

RECV (?task,?READY(autolocked)); ...

−− réception gardée par une condition

only if state == dealingthen

RECV (?task,?COMMIT (purge)ofmessage); ...

[]

...

end select end loop

Cette approche n’est pas directement transcriptible dans notre implémentation en C. À la place, notre implémentation accepte toujours de réceptionner n’importe quel type de message. Le flot d’exécution est aiguillé selon le type du message, après la réception. Dans la spécification LNT des portes et des managers, les réceptions gardées par des conditions indiquent que certains types de messages ne devraient jamais pouvoir être reçus dans certains états du processus. Dans notre implémentation en C, qui accepte tous les types de messages, la réception d’un type de message incompatible avec l’état courant d’un processus entraîne le déclenchement de la procédure d’arrêt d’urgence qui stoppe tout le système. L’extrait de spécification du processus porte ci-dessus se transcrit ainsi de la manière suivante dans notre implémentation :

while (DLC_RECV(message)) { switch (message->type) { case READY:

... case COMMIT:

// arrêt d’urgence si la porte n’est pas en train de négocier DLC_ASSERT (GATE_STATE == DEALING);

... } }

4.3.2 Compatibilité et fusion des offres

Lorsqu’une porte recherche une action réalisable, elle doit tester non seulement l’état des tâches mais aussi leurs offres. De plus, lorsque des offres compatibles sont détectées, la porte doit fusionner ces offres pour produire les offres de négociations.

Contrairement à la spécification du processus porte présenté à la section 2.7.2, notre im-plémentation du protocole traite bien les offres. Nous avons définis les fonctions de test de compatibilité entre deux offres, et de fusion de deux offres. Nous donnons ici une défini-tion mathématique de ces foncdéfini-tions. L’implémentadéfini-tion en C de ces foncdéfini-tions est ensuite relativement directe.

Offre. Une offre est un tripleto = (m, t, v), où : — m est le mode de l’offre (envoi ou réception) — t est le type de l’offre

4.3. Implémentation du protocole 105 — v est la valeur de l’offre si l’offre est en mode envoi, ou⊥ (indéfini) si l’offre est en

mode réception

Compatibilité entre deux offres. Deux offres o1 = (m1, t1, v1) et o2 = (m2, t2, v2) sont dites compatibles si et seulement si :

— les deux offres ont le même type : t1 =t2

— si les deux offrent sont en mode envoi, alors leur valeur est égale : (m1 =m2 = envoi)⇒v1 =v2

Fusion de deux offres. Deux offres o1 = (t1, m1, v1) et o2 = (t2, m2, v2) peuvent être fusionnée si et seulement si elles sont compatibles, et leur fusion résulte en une nouvelle offre o= (t, m, v) où :

t =t1 =t2

m =

(

réception si m1 =m2 = réception envoi si m1 = envoi ou m2 = envoi

v = v1 si m1 = envoi v2 si m2 = envoi ⊥ si m1 =m2 = réception

4.3.3 Action interne et option de progrès maximal

Dans la spécification LNT du manager, la réalisation d’une action interne se fait dans une branche du choix non déterministe de la boucle principale. Pour remédier au manque d’opérateur de choix non déterministe en C, dans notre implémentation la réalisation d’une action interne est, dans le cas général, conditionnée par un tirage au hasard.

Lorsque seule une action interne est possible, notre implémentation prend le temps de refuser les éventuelles demandes de verrou qui lui sont parvenues. Ensuite, elle réalise directement l’action interne.

Lorsqu’une action interne et une ou plusieurs actions sur portes sont possibles, notre im-plémentation laisse la possibilité aux actions sur porte de se réaliser en attendant des demandes de verrou pour ces actions. Quand une demande de verrou est reçue, la tâche tire au hasard pour savoir si elle accepte cette demande de verrou, ou bien si elle la refuse et effectue une action interne. Il se peut que les négociations échouent, et que, comme nous en avons discuté dans la section 2.3.4, l’action interne soit finalement la seule action réa-lisable. Pour régler ce cas, si aucune négociation n’a abouti après un certain délai, et si la tâche n’est pas couramment verrouillée, alors la tâche effectue à coup sûr l’action interne. Ce comportement peut être influencé par une option de progression maximale (maximal progress), qui peut être choisie par l’utilisateur au démarrage de l’implémentation. Cette option a pour effet de rendre les actions internes prioritaires devant les actions sur porte. En pratique, dès lors qu’une action interne est possible, notre implémentation refuse les éventuelles demandes de verrou en attente, puis elle réalise cette action interne. Si d’autres

actions sur porte étaient possibles, la tâche n’aura même pas envoyé de message “ready” pour ces actions.