

If you are commited to the domain of dynamic updates or reconfiguration, I invite you to submit a paper to HotSWUp http://www.hotswup.org/2011/...
Une partie importante de mon activité de recherche actuelle porte sur des travaux dans le cadre de la reconfiguration. Ces travaux sont menés en collaboration avec Jérémy Buisson (page web). Cette page a pour objectif de présenter la notion de reconfiguration et les travaux que nous mènons dans ce cadre.
TODO
Cet état de l'art est scindé en deux parties. Nous commençons par présenter d'abord les outils de modification dynamique des liaisons (dynamic rebinding). Puis nous détaillons les travaux effectués dans le cadre des modèles de composant.
Plusieurs environnements permettent de remplacer de manière atomique un élément d'un programme. Par exemple, Erlang [2] permet de remplacer le corps des fonctions exportées par un module. Les infrastructures de la machine virtuelle Java et de la CLR .NET Framework permettent de redéfinir le corps des méthodes d'une classe. En Smalltalk, la méthode become permet de substituer un objet. Dans tous les cas, le système met en place les indirections et le contrôle requis de manière à assurer l'atomicité de la mise à jour.
Ces mécanismes ont une granularité du niveau de la méthode ou de la fonction. Ils requièrent généralement que la méthode ou la fonction ne change pas de type. Ces systèmes peuvent permettre de remplacer plusieurs méthodes ou fonctions en une seule opération atomique. En Erlang, la portée de l'opération est limitée à un unique module.
La sémantique précise de l'atomicité de l'opération de mise à jour dépend du mécanisme. Le principal point de variation concerne le comportement vis-à-vis des appels actifs au moment de la mise à jour. Dans les machines virtuelles Java, la spécification indique que l'exécution des appels actifs continue avec l'ancienne version, conduisant à une cohabitation des versions successives. Par ailleurs, des systèmes comme Erlang permettent d'expliciter des appels qui court-circuitent l'indirection, de sorte qu'il est possible d'exécuter les anciennes versions même après la mise à jour.
Lorsqu’une application est structurée sous la forme d’un assemblage de composants logiciels, il est possible de s’appuyer sur cette structure pour faire des reconfigurations. Les mises à jour consistent alors à ajouter et enlever des composants et des liaisons dans l’assemblage. Parfois, on définit une opération de remplacement afin par exemple d’expliciter le fait que les états des composants remplacés sont préservés.
Par exemple, dans le modèle de composants Fractal [4], le contrôleur de chaque composant fournit des interfaces pour restructurer le composant contrôlé. Dans OpenCOM [8], les opérations de reconfiguration sont laissées à la charge des canevas de composants. Les différents concepts (composant, interface et liaison) sont réifiés pour pouvoir manipuler l’application à l’exécution.
Pour éviter les interférences avec l’exécution de l’application, les composants affectés par une reconfiguration sont isolés. Leur exécution est suspendue. Suspendre ainsi les composants permet par ailleurs d’échapper temporairement aux règles de validité des assemblages. Des propriétés telles que la quiescence [10] et la tranquilité [11] indiquent quels sont les composants qui doivent être effectivement arrêtés. Ces propriétés assurent que les composants concernés par les mises à jour ne sont ni actifs ni activés pendant les reconfigurations. En plus des composants directement concernés par la reconfiguration, la quiescence impose de suspendre les clients de ces composants afin d’éviter qu’ils initient des requêtent à destination des composants reconfigurés. Lorsque c’est possible, la tranquilité relâche cette contrainte en permettant le traitement des requêtes par le voisinage tant que ces requêtes soit ne font plus intervenir les composants reconfigurés, soit n’ont pas encore fait intervenir ces composants. Plus généralement, il s’agit de garantir que les reconfigurations se comportent comme des transactions insérées dans l’application, c’est-à-dire que les reconfigurations doivent notamment être atomiques même lorsque plusieurs composants sont concernés.
Le travail sur l’ingénierie des reconfiguration a fait l’objet d’une publication [5]. La figure 1.1 présente le processus d’ingénierie des reconfigurations proposé. Ce processus couvre les phases de conception, de développement et de validation des reconfigurations.
Dans ce processus, nous mettons en évidence deux formes pour les reconfigurations. À un haut niveau d’abstraction, les plans de reconfiguration considèrent le modèle Synoptic du logiciel. De même que les modèles Synoptic servent de base à la génération de code, les plans de reconfiguration sont utilisés pour produire des scripts. De plus bas niveau, les scripts mettent en œuvre les reconfigurations en prenant en compte les détails d’implémentation du code généré du logiciel de vol. Les deux générateurs de code (application et reconfiguration) doivent donc être coordonnés (figure 1.2).
Vérification d’une reconfiguration Dans le processus, nous reconnaissons le besoin de vérifier et valider les reconfigurations. Plusieurs propriétés sont à vérifier :
Les propriétés à vérifier peuvent être de l’ordre des contraintes spatiales et temporelles. Il peut s’agir de contraintes d’ordonnançabilité et de qualité de service. Il peut également s’agir de propriétés fonctionnelles.
Ces vérifications peuvent être réalisées soit au niveau abstrait, soit après la phase de génération de code. La validation formelle d’un plan de reconfiguration peut être réalisée de manière globale, en prouvant la conformité du résultat de son exécution vis-à-vis des spécifications visées. Pour limiter l’étendue des vérifications à réaliser, il est possible d’exploiter les éléments inchangés dans la spécification et dans le code. Il est possible d’optimiser encore cet objectif de preuve en se dotant d’un support de vérification modulaire, un système de contrats, par exemple [9].
Afin de déterminer l’étendue des vérifications à réaliser, à la manière d’un Makefile, un outil de configuration basé sur un système de contrat mettrait en œuvre la méthodologie suivante, et ce pour chaque module à reconfigurer :
Ceci comme le ferait un Makefile en vérifiant la conformité des dates de modules.
Une stratégie alternative est possible :
Compilation de la reconfiguration L’empaquetage de la reconfiguration contient le script de bas niveau. La reconfiguration peut par ailleurs insérer de nouveaux éléments dans le logiciel. Les nouvelles portions de code sont empaquetées avec la reconfiguration, qui est ainsi auto-contenue. Ce nouveau code répond à des besoins identifiés lors de la conception de la reconfiguration. Son développement suit le processus standard utilisé pour l’ingénierie de la version initiale du logiciel.
En amont des phases de vérification et de génération de code, nous définissons une phase de conception de la reconfiguration. Pour cette phase, qui doit résulter en une première version du plan de reconfiguration, nous identifions trois démarches possibles.
![]() |
![]() |
| Figure 1.3: Dirigée par le modèle du logiciel. | Figure 1.4: Dirigée par la reconfiguration. |
Les mécanismes habituels de reconfiguration ne sont pas appropriés pour plusieurs raisons :
Pour répondre à ces problèmes, nous avons pris comme modèle un langage fonctionnel fortement typé (Caml) afin de concevoir un mécanisme spécifique de reconfiguration (ReCaml [7]). Même si ce langage n’est pas synchrone, il partage avec Synoptic des caractéristiques telles que le typage et la décomposition fonctionnelle des programmes.
Lors de la conception de ReCaml, nous avons adopté plusieurs principes. Tout d’abord, nous faisons de la reconfiguration un véritable programme à part entière. Cela signifie notamment que nous en acceptons toute la complexité, quitte à fournir ultérieurement des outils. Dans ce programme, le développeur doit expliciter intégralement le comportement de la reconfiguration. En n’acceptant aucun comportement implicite, la reconfiguration est plus facilement prédictible. Enfin, la reconfiguration doit au moins pouvoir être typée statiquement. Il s’agit d’une première étape pour rendre les reconfigurations plus fiables.
Fondations Avec ReCaml, le fonctionnement des reconfigurations est le suivant. Lorsqu’une reconfiguration doit être exécutée, les nouveaux segments de code sont tout d’abord chargés en mémoire. Puis l’exécution de l’application est suspendue. Son état est réifié. Il est passé en paramètre pour exécuter le code de la reconfiguration, que nous appelons compensation. La compensation a pour rôle de faire le nécessaire, en fonction de l’état dans lequel l’exécution a été suspendue, afin d’intégrer la reconfiguration dans l’exécution. Pour cela, la compensation peut employer tous les moyens à sa disposition, tels que finir des activités en cours ou exécuter des fonctions spécifiques. Lorsque la compensation estime sa tâche accomplie, l’état capturé est rechargé afin de reprendre l’exécution de l’application.
Dans ReCaml, la réification de l’état de l’exécution est basée sur le concept de continuation. Le traitement de cet état repose sur sa décomposition en activations de fonctions, c’est-à-dire en environnements d’évaluation des appels. La compensation fonctionne donc en dépilant les appels de l’état capturé jusqu’à ce que soient traités tous les appels qui doivent l’être. Autrement dit, la compensation parcourt le graphe d’appel du logiciel à partir de l’état auquel la reconfiguration est déclenchée. L’environnement d’évaluation et le « reste à faire » sont identifiés grâce au site de retour. Un type statique est associé à chaque site de retour afin de pouvoir vérifier que l’opération de décomposition des continuations est utilisée correctement.
match_cont » pour décomposer une continuation.
La figure 1.5 résume le mécanisme de base « match_cont ». La continuation passée en paramètre, dont l’exécution doit se poursuivre au site de retour
Dans la figure 1.5, tous les types sont statiques. Aucun système de types dynamiques n’est introduit afin de pouvoir réaliser les vérifications dès la compilation de la reconfiguration.
Dans le graphe d’appel, chaque site de retour désigne l’arc d’un appel. Le squelette algorithmique de la compensation d’une reconfiguration repose donc sur des fonctions associées aux nœuds du graphe d’appel. La fonction associée au nœud f est mise en œuvre par une opération « match_cont », qui contient autant de clauses que d’arcs arrivant à ce nœud f. Chaque clause est responsable de compenser l’arc correspondant, réalisant une étape dans le parcours du graphe d’appel.
La compensation d’un arc peut prendre diverses formes. C’est le développeur de la reconfiguration qui doit préciser ce qui doit être fait. S’il faut poursuivre l’exécution de l’ancienne version, la sous-continuation qui capture l’appel au sommet de la pile peut être exécutée. Sinon, le développeur doit écrire le code correspondant au comportement souhaité. Pour cela, le développeur peut s’inspirer du code des différentes versions. Une des opérations classiques que pourra réaliser le développeur de la reconfiguration est la conversion des données qui ont besoin d’être convertie (entre l’ancienne version et la nouvelle).
Dans notre approche, le développeur a une maîtrise totale du comportement effectif de la reconfiguration, au prix d’une complexité de définition d’une reconfiguration accrue. Mais, nous sommes persuadés qu’en général, il existe des patrons de reconfiguration assez classiques (surtout dans le cadre d’un domaine précis) et que nous pourrons ainsi proposer au-dessus de ce mécanisme des outils simplifiant la définition des reconfigurations simples. La complexité ne devra être affrontée par le développeur de la reconfiguration que dans les cas où la reconfiguration est complexe ou ne correspond à aucun patron identifié.
Illustration Pour illustrer ces concepts, nous avons conçu une preuve de concept basée sur le calcul d’un nombre de Fibonacci. Ainsi, un code classique et naïf comme il se rencontre dans tous les tutoriaux de programmation est exécuté.
Une première mise à jour consiste à changer le type de retour de la fonction afin d’éviter les dépassements de capacité. Pour cette mise à jour, la compensation de chaque arc consiste à convertir les résultats intermédiaires vers le nouveau type de données et à terminer le calcul en utilisant la nouvelle version de la fonction (qui manipule des éléments du nouveau type). L’introspection de la pile d’appels permet de déterminer le contexte d’évaluation afin de détecter les résultats intermédiaires incorrects dûs à un dépassement de capacité. Cette détection permet de ne recalculer que les résultats effectivement incorrects.
La seconde mise à jour réalisée consiste à passer de l’algorithme naïf à 2 appels récursifs, de complexité exponentielle, à l’algorithme basé sur une fenêtre glissante de 2 nombres de Fibonacci, de complexité linéaire. Pour accomplir cette mise à jour, il faut trouver parmi les résultats intermédiaires 2 nombres de Fibonacci consécutifs. La nature de l’algorithme initial ne donne pas de moyen immédiat pour obtenir ces résultats. Il est nécessaire de parcourir l’ensemble des résultats intermédiaires, qui sont répartis sur l’ensemble des appels actifs. À nouveau, c’est bien l’introspection de la pile d’exécution qui permet de construire l’état à partir duquel l’exécution peut reprendre.
Cet exemple est décrit en partie dans [7] et un article plus détaillé est en cours de rédaction pour présenter le système réalisé.
Chaque concept architectural définit des opérations de reconfiguration spécifiques. Il se traduit par un élément du mécanisme de reconfiguration propre, comme le montre la figure 1.6. Par exemple, le concept de hiérarchie de l’architecture définit des opérations pour placer les éléments les uns à l’intérieur des autres ; le concept d’assemblage définit des opérations pour connecter et déconnecter les éléments ; le concept d’instance définit des opérations pour créer et détruire des éléments. En dissociant de la sorte les différents concepts architecturaux, nous apportons une réponse modulaire à la conception du mécanisme de reconfiguration de l’architecture. Il est ainsi possible de suivre les évolutions de Synoptic.
La conception modulaire du mécanisme est reflétée dans une construction modulaire du langage de reconfigurations. Comme le montre la figure 1.7, à partir d’un langage généraliste, nous proposons d’ajouter les éléments de langage qui correspondent aux opérations souhaitées. Pour chaque concept architectural, ce sont à la fois des constructions d’introspection et des constructions de reconfigurations qui sont ajoutées.
Ce travail a été présenté à une conférence [6].
Le mécanisme proposé répond partiellement aux problèmes spécifiques posés dans le cadre de SPaCIFY. Il permet de s’affranchir de la modularité du code. Le comportement effectif est explicite et prédictible. Nous n’avons cependant pas encore étudié la prise en compte des contraintes temporelles ni les problèmes d’ordonnancement des reconfigurations. Le mécanisme actuel ne traite pas les applications multi-tâches. En formalisant le mécanisme et en introduisant un typage statique, nous contribuons à la construction de futurs procédés de vérification des reconfigurations, sans pour autant apporter de réponse complète. Ces problèmes (temps-réel, ordonnancement, multi-tâche et vérification) restent actuellement ouverts.
Même s’il permet d’abstraire par des constructions langagières les détails d’implémentation, le mécanisme proposé est de bas niveau. Le développeur de reconfiguration doit avoir une bonne connaissance des différentes versions du logiciel de vol afin par exemple de mettre en œuvre le parcours du graphe d’appel inversé. Nous pensons qu’un outillage est impératif afin de rendre l’approche viable. À relativement court terme, il est par exemple tout à fait envisageable de générer automatiquement le squelette algorithmique qui réalise le parcours du graphe d’appel d’un programme particulier. De même, nous commençons à identifier des comportements récurrents dont l’automatisation de la génération épargnerait au développeur certaines tâches systématiques. C’est le cas par exemple lorsqu’on souhaite simuler un comportement de dynamic rebinding.
Dans le cadre des résultats de SPaCIFY, le mécanisme ReCaml est au niveau de la preuve de concept. Le langage support est un λ-calcul typé rudimentaire, privé par exemple du polymorphisme. Des travaux concernant les mécanismes fondamentaux de ReCaml semblent indispensables avant de pouvoir intégrer un langage de programmation réaliste.
[1] First ACM Workshop on Hot Topics in Software Upgrades, Nashville, Tennessee, USA, octobre 2008.
[2] Joe Armstrong : Programming Erlang: software for a concurrent world. The Pragmatic PRogrammers, 2007.
[3] Loïc Besnard, Thierry Gautier et Jean-Pierre Talpin : Spa5 bis - code generation strategies for synoptic using the polychrony platform, 2009.
[4] Eric Bruneton, Thierry Coupaye, Matthieu Leclercq, Vivien Quéma et Jean-Bernard Stefani : The Fractal component model and its support in Java. Software: Practice and Experience, 36(11-12):1257–1284, septembre 2006. Spec. issue on experiences with auto-adaptive and reconfigurable systems.
[5] Jérémy Buisson, Cecilia Carro et Fabien Dagnat : Issues in applying a model driven approach to reconfigurations of satellite software. In First ACM Workshop on Hot Topics in Software Upgrades [1].
[6] Jérémy Buisson et Fabien Dagnat : Experiments with Fractal on modular reflection. In Software Engineering Research, Management and Applications, pages 179–186, Prague, Czech Republic, août 2008.
[7] Jérémy Buisson et Fabien Dagnat : Introspecting continuations in order to update active code. In First ACM Workshop on Hot Topics in Software Upgrades [1].
[8] Geoff Coulson, Gordon Blair, Paul Grace, Francosi Taiani, Ackbar Joolia, Kevin Lee, Jo Ueyama et Thirunavukkarasu Sivaharan : A generic component model for building systems software. ACM Transactions on Computer Systems, 26(1), février 2008.
[9] Yann Glouche, Paul Le Guernic, Jean-Pierre Talpin et Thierry Gautier : A boolean algebra of contracts for logical assume-guarantee reasoning. Rapport technique, INRIA, 2008.
[10] Jeff Kramer et Jeff Magee : The evolving philosophers problem: dynamic change management. IEEE Transaction on Software Engineering, 16(11):1293–1306, novembre 1990.
[11] Yves Vandewoude, Peter Ebraert, Yolande Berbers et Theo D’Hondt : Tranquility: a low disruptive alternative to quiescence for ensuring safe dynamic updates. IEEE Transactions on Software Engineering, 33(12):856–868, décembre 2007.