• Aucun résultat trouvé

Les implémentations générées par DLC sont encore limitées sur quelques aspects. Cette section présente succinctement les trois limitations principales et les perspectives d’amé-liorations envisagées.

4.5. Limitations actuelles de DLC 107

Absence de types “complexes” dans les offres

Les types de données “complexes” tels que les enregistrements, les listes ou les tableaux, sont utilisables dans les spécifications des tâches mais ils ne doivent pas apparaître dans les offres d’actions. En effet, pour le moment DLC accepte dans les offres uniquement des valeurs de types simples comme le type booléen, le type entier, ou encore les types énumérés définis par l’utilisateur. En cas de présence d’un type complexe, une erreur est levée dès la compilation.

Cette limitation est notamment due à la nécessité de transmettre les valeurs des offres sur le réseau. Pour pouvoir gérer les offres d’un type complexe, il faut être capable sérialiser (c’est-à-dire de représenter en une chaîne d’octets) n’importe quelle valeur de ce type, et de dé-sérialiser une chaîne d’octets en une valeur. L’implémentation en C des types et des fonctions LNT est automatiquement générée par les outils de CADP, mais cette implémentation ne contient pas de fonctions de sérialisation. Il est envisageable de générer ce genre de fonctions au niveau de DLC, mais il est délicat de traiter les types complexes dont la taille de la représentation des valeurs est variable. Nous estimons que la production des fonctions de sérialisation doit être assurée au niveau des outils CADP, qui ont déjà une connaissance précise de la manière dont chaque type LNT est représenté en C.

Absence de gestion des gardes de communication sur les actions

En LNT, une action est éventuellement gardée par une condition qui peut dépendre de variables du processus, parmi lesquelles certaines sont des offres en réception. DLC ne prend pas en compte ces gardes car l’interface EXEC/CÆSAR est trop limitée pour pouvoir les traiter correctement.

L’interface EXEC/CÆSAR permet d’indiquer une valeur pour une offre en mode réception. En cas de garde faisant référence à cette valeur, il n’est pas possible de tester si la valeur est valide pour la garde, sans réaliser l’action au passage, si cette valeur est effectivement valide. DLC ne peut donc pas tester une valeur d’offre en réception sans éventuellement déclencher la réalisation de l’action.

Par exemple, l’action suivante sur la porte A peut avoir lieu uniquement si l’entier “x”, reçu lors de l’action, est plus grand que l’entier “y” :

var x, y: nat in

y := ... ;

A (?x)where x > y; ...

end var

Le fait de ne pas pouvoir tester différentes valeurs de “x” par rapport à la garde sans éventuellement déclencher l’action rend impossible le test de différentes valeurs proposées pour différentes négociations.

Pour pouvoir traiter correctement les gardes, il faut modifier l’outil EXEC/CÆSAR afin que son interface donne accès aux conditions de gardes.

On note néanmoins qu’une garde qui ne dépend pas d’une valeur reçue lors de l’action peut être transformée en une pré-condition, et autorise ainsi l’utilisation de DLC. Par exemple, la garde suivante peut être tranformée en une condition précédent l’action :

−−La garde est indépendante de la valeur de "x" reçue pendant l ’ action ...

A (?x, y) where y == z

−−... elle peut donc être transformée en une condition évaluée avant l ’ action

only if y == zthen

A (?x, y)

end if

Absence de création dynamique de tâches

Le nombre de tâches est une constante définie par la composition parallèle donnée en entrée. Une tâche qui contient elle-même une composition parallèle pourrait donner naissance à plusieurs nouvelles tâches de manière dynamique. Cependant, l’utilisation du compilateur EXEC/CÆSAR impose une implémentation séquentielle de cette composition parallèle au sein de la tâche. DLC ne gère donc pas la création dynamique de tâche, ce qui nécessiterait notamment de grands changements au sein du compilateur EXEC/CÆSAR. De plus, les outils de vérification de CADP gèrent uniquement des spécifications dont le nombre de processus est borné et connu statiquement. Néanmoins, il est déjà possible en de traiter de nombreux systèmes en pratique sans être confronté à cette limite.

Chapitre 5

Vérification formelle de protocole de

synchronisation

A program that produces incorrect results twice as fast is infinitely slower.

John Ousterhout

Nous avons élaboré une méthode de vérification formelle de protocole de synchronisation. L’approche générale de notre méthode est de vérifier l’absence de mauvais comportements dans l’espace d’états du modèle d’une implémentation utilisant un protocole de synchro-nisation. La méthode est structurée en deux phases principales : dans un premier temps, nous produisons, à partir d’une spécification de système distribué, le modèle d’une implé-mentation de ce système. Dans un deuxième temps, nous conduisons plusieurs vérifications formelles sur ce modèle.

Nous avons commencer à développer cette méthode dès le début de la thèse, afin de pou-voir vérifier des protocoles existants. Nous avons ainsi analysé plusieurs protocoles de la littérature [EL13], en découvrant au passage que le protocole de Parrow et Sjödin peut mener à des interblocages en cas de communications asynchrones. Nous avons découvert en milieu de thèse qu’une méthode de vérification de protocole pour le rendez-vous mul-tiple avaient déjà été proposée [GGvB+95]. Toutefois, cette méthode a été appliquée sur différents protocoles que ceux que nous avons retenu pour notre étude, et elle repose sur des outils qui ne sont plus maintenus aujourd’hui.

Notre méthode de vérification nous a ensuite été d’un grand soutien lors de la mise au point du protocole utilisé dans DLC. Elle n’a donc pas seulement été utilisée pour une étude a posteriori de protocoles existants, mais aussi directement au moment de l’élaboration de notre protocole. Nous avons ainsi pu itérer sur les différentes améliorations présentées au chapitre 2, en vérifiant à chaque étape que nous n’introduisions pas de problèmes au sein du protocole. De plus, nous avons continué d’affiner notre méthode tout au long de la thèse.

Dans ce chapitre, nous décrivons notre méthode de vérification et ses résultats. La pre-mière section présente la génération, à partir d’une spécification de système, du modèle de l’implémentation de ce système. La deuxième section couvre les vérifications formelles conduites sur ce modèle. Enfin, la troisième section expose les résultats de l’application de notre méthode.

5.1 Génération de modèle d’une implémentation

À partir de la spécification LNT d’un système distribué, on produit automatiquement une autre spécification LNT qui modélise l’implémentation du système. Cette implémentation utilise le protocole de synchronisation pour gérer les actions du système. Cette génération de code est proche de celle présentée au chapitre 4, à la différence qu’on produit cette fois un modèle LNT plutôt qu’une implémentation en C1.

Le modèle d’une implémentation est composé des éléments suivants : — le modèle de chaque tâche associée à un manager

— le modèle de chaque porte

— le modèle des communications par passage de messages asynchrones

Pour le modèle d’un manager et d’une porte, on utilise les processus LNT déjà exposés dans la section 2.7. Le processus manager requiert en argument l’espace d’états de sa tâche, et le manager et la porte nécessitent les vecteurs de synchronisation du système. La suite de cette section présente comment on obtient un modèle LNT de ces arguments, ainsi que la mise en place de communications asynchrones au sein du modèle.

5.1.1 Modèle d’une tâche associée à un manager

Le comportement d’un manager est spécifié par un processus LNT générique qui prend en argument l’espace d’états de la tâche pour laquelle il doit négocier. On doit donc générer, à partir du processus LNT qui décrit le comportement d’une tâche dans le système distribué, une structure de données LNT qui représente l’espace d’états de cette tâche.

Un espace d’états est un ensemble de transitions, dont chaque transition est un triplet contenant un état d’origine, une action, et un état de destination. Dans le cadre du modèle de l’implémentation, on gère uniquement les actions sans offres : une action se résume donc simplement à un identifiant de porte. Nous avons développé un programme qui s’appuie sur l’interface OPEN/CÆSAR pour produire automatiquement une structure de données modélisant le LTS d’une tâche.

1. Dans le cadre de BIP, une transformation de modèle a systématiquement lieu pour obtenir un mo-dèle comportant un protocole de synchronisation. Ce momo-dèle est ensuite transformé à son tour en une implémentation (voir la remarque dans la section 4.3)

5.1. Génération de modèle d’une implémentation 111 Considérons par exemple la tâche FOO spécifiée de la manière suivante :

processFOO [A, B, C: none] is select A [] B end select; C; stop end process A B C

Notre programme génère automatiquement le modèle de l’espace d’états de cette tâche sous la forme d’une structure de données LNT retournée par une fonction :

functionLTS_FOO : transition_setis return{

transition (0, action (DLC_GATE_A), 1), transition (0, action (DLC_GATE_B), 1), transition (1, action (DLC_GATE_C), 2) }

end function

De plus, nous avons aussi défini les fonctions qui permettent d’accéder aux actions possibles à partir de l’état courant de la tâche (“possible_actions”), et à la liste des états atteignables après une certaine action depuis l’état courant (“get_next”). La définition de ces fonctions est disponible dans l’Annexe A. Le modèle de la tâche FOO associée à son manager peut ainsi être obtenu en passant la structure de données “LTS_FOO” au processus manager générique.

5.1.2 Modèle des vecteurs de synchronisation

Les processus managers et portes requièrent en argument les vecteurs de synchronisation du système. Ces vecteurs sont représentés par une structure de données en LNT. La liste des vecteurs de synchronisation de chaque porte est stockée dans une constante, qui est passée en argument au processus porte correspondant. De plus, toutes ces listes sont regroupées dans une “carte” (map) de synchronisation qui est passée aux processus managers.

Considérons par exemple un système composé de deux tâches au comportement défini par FOO et dont les actions sur la porte C sont synchronisées :

par A#2, Bin

C−> FOO [A,B,C]

| | C−> FOO [A,B,C]

| | FOO [A,B,C]

Les vecteurs de synchronisation de ce système sont représentés de la manière suivante dans le modèle de l’implémentation (on remarque qu’il est possible d’utiliser plusieurs fois la même tâche, ici FOO, au niveau de la spécification donnée en entrée) :

functiongate_A_sync_vect : sync_vect_list is return{ { DLC_TASK_0_FOO, DLC_TASK_1_FOO }, { DLC_TASK_1_FOO, DLC_TASK_2_FOO }, { DLC_TASK_2_FOO, DLC_TASK_0_FOO } } end function

functiongate_B_sync_vect : sync_vect_list is return{

{ DLC_TASK_0_FOO, DLC_TASK_1_FOO, DLC_TASK_2_FOO } }

end function

functiongate_C_sync_vect : sync_vect_list is return{

{ DLC_TASK_0_FOO, DLC_TASK_1_FOO } }

end function

functionglobal_sync_map : sync_mapis return{

sync_map_entry (DLC_GATE_A, gate_A_sync_vect), sync_map_entry (DLC_GATE_B, gate_B_sync_vect), sync_map_entry (DLC_GATE_C, gate_C_sync_vect) }

end function

Ces constantes sont ensuite passées aux différents processus portes et managers afin qu’ils puissent accéder aux vecteurs de synchronisation du système.

5.1.3 Modèle des communications asynchrones

Les communications asynchrones entre les portes et les managers sont modélisées à l’aide de processusbuffers (tampons de communication). Ces processus buffers jouent le rôle des sockets TCP utilisées dans une implémentation réelle ; ils modélisent une file de messages sans perte. Afin de borner l’espace d’états du modèle de l’implémentation, nous limitons la taille de chaque file par une constante globale “bufsize”. Nous faisons ensuite en sorte que toute communication entre un manager et une porte, ou entre managers, passe par le biais d’un processus buffer.

5.1. Génération de modèle d’une implémentation 113

5.1.4 Modèle général de l’implémentation

Le modèle général de l’implémentation est constitué de la composition parallèle des pro-cessus managers, portes et buffers. Les portes et les managers ne se synchronisent jamais directement entre eux, car leurs communications passent toujours par le biais d’un buffer. De la même manière, deux buffers ne se synchronisent jamais entre eux, car ils servent de pont de communication entre deux processus managers ou portes. La composition parallèle générale est donc formée de deux compositions parallèles imbriquées, avec d’un côté tous les processus buffers, et de l’autre tous les processus portes et managers.

Pour illustrer cette composition, on reprend le système formé de deux tâches FOO déjà utilisé en exemple précédemment. Le modèle général de l’implémentation de ce système a la forme suivante : processMAIN [ TASK_0_FOO_SEND, TASK_0_FOO_RECV, TASK_1_FOO_SEND, TASK_1_FOO_RECV, TASK_2_FOO_SEND, TASK_2_FOO_RECV, GATE_A_SEND, GATE_A_RECV, GATE_B_SEND, GATE_B_RECV, GATE_C_SEND, GATE_C_RECV : com, ACTION, HOOK_REFUSE : annonce ]is par TASK_0_FOO_SEND, TASK_0_FOO_RECV, TASK_1_FOO_SEND, TASK_1_FOO_RECV, TASK_2_FOO_SEND, TASK_2_FOO_RECV, GATE_A_SEND, GATE_A_RECV, GATE_B_SEND, GATE_B_RECV, GATE_C_SEND, GATE_C_RECV in par buffer [TASK_0_FOO_SEND,TASK_1_FOO_RECV](DLC_TASK_0_FOO,DLC_TASK_1_FOO) | | buffer [TASK_0_FOO_SEND,TASK_2_FOO_RECV](DLC_TASK_0_FOO,DLC_TASK_2_FOO) | | buffer [TASK_1_FOO_SEND,TASK_0_FOO_RECV](DLC_TASK_1_FOO,DLC_TASK_0_FOO)

| | ... −−connections entre les managers

| | buffer [TASK_0_FOO_SEND,GATE_A_RECV](DLC_TASK_0_FOO,DLC_GATE_A)

| | buffer [GATE_A_SEND,TASK_0_FOO_RECV](DLC_GATE_A,DLC_TASK_0_FOO)

| | buffer [TASK_0_FOO_SEND,GATE_B_RECV](DLC_TASK_0_FOO,DLC_GATE_B)

| | buffer [GATE_B_SEND,TASK_0_FOO_RECV](DLC_GATE_B,DLC_TASK_0_FOO)

| | ... −−connections entre les managers et les portes

end par | |

par

MANAGER [ACTION, TASK_0_FOO_SEND, TASK_0_FOO_RECV] (DLC_TASK_0_FOO, task_FOO_state_space, global_sync_map)

| | MANAGER [ACTION, TASK_1_FOO_SEND, TASK_1_FOO_RECV] (DLC_TASK_1_FOO, task_FOO_state_space, global_sync_map)

| | MANAGER [ACTION, TASK_2_FOO_SEND, TASK_2_FOO_RECV] (DLC_TASK_2_FOO, task_FOO_state_space, global_sync_map)

(DLC_GATE_A, gate_A_sync_vect)

| | GATE [GATE_B_SEND, GATE_B_RECV, ACTION, HOOK_REFUSE] (DLC_GATE_B, gate_B_sync_vect)

| | GATE [GATE_C_SEND, GATE_C_RECV, ACTION, HOOK_REFUSE] (DLC_GATE_C, gate_C_sync_vect)

end par end par end process

On obtient ainsi un modèle complet d’une implémentation qui utilise le protocole de syn-chronisation, et où les managers et les portes communiquent par passage de messages asynchrones. La génération de ce modèle à partir de la spécification d’un système distribué est entièrement automatisée.

5.2 Vérifications conduites sur le modèle