• Aucun résultat trouvé

9.3 T HINK Join : Un modèle de programmation à base de composants pour applications de

9.3.2 Le langage JoinDSL

Le langage JoinDSL est basé sur le Join-Calcul et contient un certain nombre d’enrichissements qui ont pour but de simplifier la programmation des composants. Une description complète de Join-Calcul est présentée dans [FG96]. Nous nous proposons par la suite de donner une introduction informelle au langage JoinDSL en identifiant ses différences par rapport au Join-Calcul standard. Ensuite, nous présentons la syntaxe et la sémantique de ce langage.

9.3.2.1 Présentation générale

Le Join-Calcul repose sur l’approche reflexive chemical abstract machine (CHAM). D’après le mo-dèle CHAM, l’état d’un système est représenté par une soupe chimique contenant des événements activés ainsi que des processus en cours d’exécution. L’avancement de l’état du système est effectué par le dé-clenchement de l’exécution de certains processus lors que certains événements sont activés dans la soupe. L’exécution du processus a pour effet de désactiver les événements de la soupe qui ont déclenché son exécution et d’en activer potentiellement d’autres. Ainsi, la description du comportement du système est décrit par des règles de réduction comme ce qui suit :

R1 = push(image)|pret(decodeur) ⊲ decodeur(image)

Nous appellerons la partie gauche du signe ⊲ un patron de synchronisation, et la partie droite une réaction. La règle ci-dessus a la sémantique suivante : si les événements (ou messages) push(i1) et

pret(decodeur) sont activés dans la soupe, alors la règle R1 va s’appliquer. Ceci va avoir comme effet

de déclencher la réaction decodeur(image) et consommer les événements se trouvant dans la partie gauche de la règle.

Maintenant, essayons de proposer une règle décrivant le comportement du contrôleur d’exécution présenté dans la figure 9.9. Une première tentative serait la suivante :

R= push(e1)|push(e2) ⊲ mix (e1, e2)

Cette règle dit que la réaction mix sera déclenchée si deux messages push sont activés dans la soupe pour fournir les paramètres e1et e2. Or, cette règle est ambiguë en ce qui concerne la source des ces deux messages. Étant donnée l’exécution parallèle des composants decodeurs, ces deux événements peuvent être produits par un seul d’entre eux, ce qui mènera à un dysfonctionnement du système. De plus, si plus de deux messages push sont activés dans la soupe au moment de l’évaluation de l’état du système, le contrôleur peut prendre n’importe quelle paire présente dans la soupe. La causalité des messages produits par un décodeur serait alors violée. Il y a donc besoin de pouvoir exprimer la source des messages en assurant une propriété de causalité entre les messages produits par une même source. Pour résoudre ce problème, nous introduisons dans le langage JoinDSL la notion d’interface qui permet de désigner un canal dans lequel doit être présent une événement activé. Ceci permet tout d’abord de sélectionner une source de messages. De plus, ces canaux ont la propriété d’ordonner les messages dans un ordre FIFO, ce qui assure la relation de causalité entre les messages postés via la même interface. On ne parlera alors plus d’une soupe globale d’événements mais plutôt d’une soupe d’événements qui sont véhiculés par des canaux (ou files de messages) ordonnés. La règle suivante décrit la règle décrivant le comportement du contrôleur d’exécution suscité conformément au langage JoinDSL.

R= I1 .push(e1)|I2 .push(e2) ⊲ I3 .mix (e1, e2)

Afin de présenter une autre difficulté concernant la programmation dans Join-Calcul standard, rajou-tons un nouvel élément dans notre architecture. Nous proposons d’enrichir l’architecture de la figure 9.9 avec un autre composant qui envoie au mixeur des sous-titres. Le comportement du mixeur est alors lé-gèrement modifié. Il propose deux réactions différentes. La réaction précédente est gardé car des images peuvent être mixées sans la présence de sous-titres. Or, si un sous-titre est présent en entrée en plus des deux images à mixer, une réaction spécifique permet de mixer l’ensemble de ces trois entrées pour obtenir une image avec sous-titre. Les règles décrivant le comportement du mixeur deviennent alors :

R1= I1 .push(e1)|I2 .push(e2)I3 .subtitle(st) ⊲ I3 .mixwithsubtitle(e1, e2, st) R2= I1 .push(e1)|I2 .push(e2) ⊲ I3 .mix (e1, e2)

9.3. THINKJoin : Un modèle de programmation à base de composants pour applications de streaming

Le problème concernant les règles ci-dessus est l’indéterminisme en ce qui concerne la sélection d’une règle à exécuter dans le cas ou les deux patrons de synchronisation des deux règles sont satisfaits

en même temps. En effet, dans le cas où la soupe d’événements contiendrait les trois messages I1.push,

I2.pushet I3.subtitle, les deux règles deviennent activables. Pour maîtriser le comportement du système dans ce type de cas, nous nous inspirons de la proposition des concepteurs de Join Java. Il s’agit d’aug-menter le calcul avec une propriété déterministe en ce qui concerne l’ordre d’évaluation. Dorénavant, les règles seront évaluées de haut en bas, ce qui permet aux programmeurs d’ordonner les priorités des règles décrivant le comportement des composants. Dans notre cas précis, la règle R1 serait exécutée si les trois événements qu’elle attend en entrée sont produits. Dans le cas contraire, la règle R2sera évaluée.

En résumé, le langage JoinDSL que nous définissons dans le cadre de nos travaux étend le Join-Calcul en deux points. Tout d’abord, nous y intégrons la notion d’interface. Cela permet d’une par d’annoter la source d’un événement en utilisant la notion de canaux de communication, et lie les messages transmis via le même canal avec une relation de causalité. La deuxième extension consiste en la définition d’un ordre d’évaluation des règles, de façon similaire à la proposition de JoinJava. Cela permet aux program-meurs d’associer des priorités aux actions à exécuter.

9.3.2.2 Syntaxe

La figure 9.10 présente la syntaxe du langage JoinDSL en utilisant un formalisme de type BNF. Les opérateurs | et ⊲ de la syntaxe originelle du Join-Calcul sont substitués dans cette syntaxe par les opérateurs&et=>, respectivement. Les commentaires à la C++ sont permis mais ne sont pas représentés dans la grammaire pour des raisons de lisibilité. Les terminaux du langage sont des expressions régulières

identiques à celle d’un identifiant en langage Java. Enfin, un mot cléemptyest défini pour décrire des

règles où la seule réaction associé à un pattern de synchronisation consiste en la consommation des événements présents en entrée.

D´efinition → R`egle +

R`egle → Pattern ‘=>’ R´eaction ‘ ;’

Pattern → Message ( ‘&’ Message )∗

Message → M ´ethode

R´eaction → M ´ethode | ‘empty’

M ´ethode → idf ‘.’ idf ‘(’ idf ∗ ‘)’

FIG. 9.10 – Grammaire BNF du langage JoinDSL.

9.3.2.3 Modèle d’évaluation des règles

La règles sémantiques associée à la syntaxe décrite dans la figure 9.10 sont détaillés dans [May06]. Nous nous proposons dans ce document de présenter le modèle d’évaluation des règles de manière in-formelle au travers d’un exemple.

Un fichier de règle correspond à la description du comportement d’un composant. Nous allons appe-ler par la suite un contrôleur d’exécution, le mécanisme d’évaluation de règles associé à un composant. Chaque composant aura alors son propre contrôleur d’exécution. Avant de s’intéresser à l’évaluation des règles, intéressons nous d’abord à l’organisation des contrôleurs d’exécution.

La figure 9.11 présente les structures de données qui sont manipulés par un contrôleur d’exécution. Toutes les interface serveurs du composant sont représentées par des files distinctes. Les événements reçus via la même interface sont ordonnés dans un ordre FIFO au sein de ces files. Les éléments se trou-vant en tête de chaque file sont activés dans la soupe d’événements. Cela permet d’évaluer les événements

dans l’ordre de leur arrivée, ce qui permet d’assurer une propriété de causalité entre les événements reçus via la même interface.

contrôleur d'exécution x2

x1 y2 z

FIG. 9.11 – Architecture d’un contrôleur d’exécution qui permet d’évaluer des règles écrits en JoinDSL.

Lorsqu’une interface serveur du composant est appelée, l’invocation de méthode est transformée en une version sérialisée2. Ainsi, il est transformé en un événement et est enregistrée dans la queue associée à l’interface appelée. Si l’enregistrement de l’événement modifie le contenu de la soupe d’événements3, il est temps de la réévaluer pour vérifier si un patron de synchronisation est satisfait. Pour ce faire, un

tableau statusmodélisant la soupe d’événements est construit. Le contenu de ce tableau est ensuite

comparé à tous les patrons reconnus, dans l’ordre de leur déclaration, jusqu’à en trouver un qui corres-pond à l’état actuel de la soupe. Dans le cas où l’on en trouve un, les événements associés sont retirés des files et un appel de méthode est construit pour déclencher la réaction associée du composant à la règle en question. La consommation de certains événements modifie l’état de la soupe. Cela peut résulter en la reconnaissance de nouveau patrons. Alors, tant que l’une des files ayant été modifiée contient encore des événements, on recommence le processus d’évaluation à partir de la première règle. Cette itération continue jusqu’à ce qu’aucun patron ne soit reconnu.

Le mot cléemptyreprésente une réaction vide. Elle a donc pour effet de consommer uniquement les

événements qui ont déclenché son exécution.

Essayons maintenant d’exécuter un scénario simple afin de mieux comprendre le fonctionnement d’un contrôleur d’exécution. Considérons un composant dont le comportement est décrit avec les règles suivantes :

i 1 . A ( ) & i 3 . E ( ) => i 5 . r 1 ( ) ; / / R1 i 1 . B ( ) & i 2 . C ( ) => i 5 . r 2 ( ) ; / / R2 i 2 . D ( ) & i 4 . F ( ) => i 5 . r 3 ( ) ; / / R3

Il s’agit d’un composant avec quatre interfaces serveur qui peut recevoir au total six types d’événe-ments différents. Trois réactions sont implantées par le composant contrôlé. Elles sont toutes accessibles

via l’interfacei5. Supposons maintenant que la séquence d’événements suivante soit produite en entrée

de ce composant :

ha1, b1, c1, d1, b2, c2, d2, f1, f2, a2, e1, e2i

La figure 9.12 présente l’évolution de l’état du contrôleur. Aucun patron n’est reconnu jusqu’à ce que l’événement e1soit produit. Les files sont remplies alors par les événements qui leur sont associées. Au

moment de la réception de l’événement e1, le patron décrit par la règleR1est reconnu. Les événements

2La version sérialisée d’un appel de méthode contient l’identificateur du méthode invoquée ainsi que les paramètres à passer.

9.3. THINKJoin : Un modèle de programmation à base de composants pour applications de streaming

AetEsont alors retirés des files concernées et la réactionr1est exécutée. La consommation des événe-ments fait évoluer l’état de la soupe : elle contient alors des événeévéne-mentsB,CetF. Cela fait que la règle de la ligne 2 est reconnue. La réactionr2est alors exécutée après avoir consommé les événementsB,C. Cela modifie encore l’état de la soupe. L’évaluation des règles continue jusqu’à ce que plus aucun patron ne soit reconnu. Dans le cas de notre exemple, l’évaluation est terminée lorsque tous les événements sont consommés. a1 b1 b2 c1 d1 c2 e1 f1 f2 d2 ? ? ? ? I1 I2 I3 I4 a1 b1 b2 c1 d1 c2 e1 f1 f2 d2 A E I1 I2 I3 I4 b1 b2 c1 d1 c2 f1 f2 d2 B C I1 I2 I3 I4 b2 d1 c2 f1 f2 d2 F D I1 I2 I3 I4 b2 c2 f2 d2 B C I1 I2 I3 I4 f2 d2 E A I1 I2 I3 I4 a2 a2 a2 a2 a2 a2 e2 e2 e2 e2 e2 d2 f2 F D I1 I2 I3 I4 I1 I2 I3 I4 ? ? ? ?

FIG. 9.12 – Illustration d’un scénario d’exécution de l’évaluation de règles.