Gérer l’exécution d’un agent

Le composant logiciel principal d’une application SKUAD est une unité logicielle qu’on appelle un agent. Cette unité peut être vu comme une sorte de « mini-programme » dans le sens ou on pourra le mettre en fonction en lançant son exécution, puis le mettre en pause, ou encore arrêter son exécution pour ensuite la relancer plus tard.

En effet, dans les programmes que l’on peut écrire avec SKUAD, on peut demander la création d’un agent à tout moment. Une fois crée l’agent est à l’arrêt (son code ne s’exécute pas) et ce jusqu’à ce qu’on demande le lancement de son exécution. Quand un agent est en cours d’exécution on peut à tout moment décider de le mettre en pause, ou d’arrêter son exécution. Et quand il ne sera plus utile, on pourra demander de détruire l’agent afin de libérer les ressources qu’il occupe.

Avec ce tutoriel nous allons voir comment procéder pour réaliser ces différentes demandes. Aussi, nous n’allons pas tout de suite voir comment écrire le code des agents, mais juste examiner comment les piloter.

1 - La classe ServerAU

Dans SKUAD, pour l’exécution en temps réel des agents, l’élément qui gère les demandes de : création, exécution, mise en pause, arrêt et destruction d’agent, est appelé serveur d’exécution d’agent ubiquitaire. Le terme de « serveur » apparait dans son nom car c’est aussi un objet avec lequel on peut interagir à distance via le réseau, mais nous verrons cela dans un prochain tutoriel.

Plus précisément, dans la librairie SKUAD, ce rôle est joué par la classe : ServerAU (package: skuad.agentu). La première fonctionnalité de cette classe est fournie par la méthode statique suivante :

public static ServerAU newServer(Hub hub)

Il s’agit d’une fabrique d’instance qui permet d’obtenir des objets serveur d’exécution d’agent ubiquitaire que l’on pourra utiliser pour créer des agents et piloter leur exécution.

Cette méthode attend en argument un objet hub. Il s’agit d’un objet qui permet de spécifier le sous-réseau virtuel sur lequel le serveur, et les agents qu’il va gérer, vont opérer. Pour le moment nous pouvons ignorer cet argument, et nous utiliserons la valeur null pour invoquer de cette méthode.

Une fois que l’on a obtenue une instance de serveur, cela nous donne la possibilité d’exploiter les méthodes de pilotage suivantes :

public int createAgent(Class<extends Agent> classe, String agent_name)

public boolean runAgent(int agentId)

public boolean stopAgent(int agentId)

public boolean pauseAgent(int agentId)

public boolean removeAgent(int agent_id)
 
 
2 - Demander la création d'un agent

Une demande de création d’agent s’effectue en déclenchant la méthode suivante sur l’objet serveur :

public int createAgent(Class<extends AgentU> classe, String agent_name)

Cette méthode prend en argument :

  • classe : l’objet qui représente la classe de l’agent à créer. La classe de l’agent est le code qui spécifie les caractéristiques de l’agent et les traitements qu’il peut réaliser (nous apprendrons à écrire de telles classes dans un prochain tutoriel). Pour l’argument de cette méthode il faut utiliser une valeur qui est l’objet qui représente cette classe. Un tel objet est fourni par la propriété publique statique de nom class de la classe de l’agent. Ainsi, si CdA est la classe de l’agent, alors CdA.class contient l’objet qui représente cette classe.
  • agent_name : permet de donner un nom à l’agent créé. Cela donne la possibilité de désigner l’agent par un nom plutôt que par un identifiant numérique. Si on ne souhaite pas donner un nom particulier à l’agent, on peut utiliser la valeur null.

La valeur retourner par cette méthode est un entier qui représente l’identifiant de l’agent créé. Il faudra utiliser cette valeur pour désigner l’agent dans les autres méthodes de pilotage (argument appelé agent_id). Mais si cette valeur vaut -1, cela indique qu’une erreur s’est produite et qu’il n’a pas été possible de créer de l’agent.

Notez que nous parlons ici d’une classe pour désigner le code de l’agent car Java est un langage essentiellement orienté objet, mais il existe d’autres façons d’exprimer le code d’un agent (en particulier dans les implémentations de SKUAD pour des langages non orienté objet). Aussi nous utiliserons plus généralement l’appellation : type de l’agent pour désigner ce code. Pour la méthode crateAgent(…) ci-dessus, le type de l’agent est la classe fournie en premier argument.

 
 
3 - Piloter l'exécution d'un agent

Le pilotage de l’exécution de l’agent s’effectue à l’aide des méthodes suivantes de l’objet serveur :

public boolean runAgent(int agentId)

public boolean stopAgent(int agentId)

public boolean pauseAgent(int agentId)

public boolean removeAgent(int agent_id)

Ces méthodes attendent toutes en argument l’identifiant de l’agent sur lequel agir, et renvoie une valeur booléenne qui vaudra true en cas de succès de l’opération, ou false sinon. Quand l’une des ces méthodes est appliquée avec succès, l’état de l’agent change.

L’état courant d’un agent peut être consulté à tout moment avec la méthode suivante de l’objet serveur :

public byte agentState(int agentId)

L’état de l’agent est une valeur entière (type byte) qui exprime dans quelle situation il se trouve en terme d’exécution. Les valeurs d’état possibles son des constantes de l’interface AgentU (package: skuad.agentu) :

  • AgentU.OFF : l’agent est à l’arrêt. C’est l’état dans lequel il se trouve juste après sa création. Il se retrouve également dans cet état à chaque fois que son exécution sera arrêtée avec la méthode stopAgent(…).
  • AgentU.RUN : l’agent est en cours d’exécution. Il passe dans cet état suite au succès d’un appel runAgent(…). L’appel de cet méthode peut avoir eu lieu suite à la création de l’agent, ou suite à une précédente demande de mise en pause.
  • AgentU.PAUSE : l’exécution de l’agent est en pause. L’agent passe dans cet état quand son exécution à été mise en pause via un appel à pauseAgent(…).
  • AgentU.NOT_EXIST : cet état indique que l’agent n’existe plus (ce qui est le cas suite à un appel à la méthode removeAgent(…)), ou n’a jamais existé (l’identifiant indiqué ne correspond pas à un agent connu).
 
 
4 - Détruire un agent

Pour détruire un agent il suffit d’utiliser la méthode suivante de l’objet serveur :

public boolean removeAgent(int agent_id)

Cette action va produire l’arrêt de l’exécution de l’agent, la suppression de toutes les ressources qu’il occupe, et son identifiant ne sera plus exploitable à l’avenir.

 
 
5 - Mise en pratique
a) Instancier et démarrer l’exécution d’un agent

Il est maintenant temps pour vous d’expérimenter cette gestion de l’exécution d’un agent. Pour cela, créez un nouveau projet SKUAD et ajoutez-y un programme qui doit lancer l’exécution d’un agent. Puisque vous ne savez pas encore implémenter vos propres agents, vous allez utiliser la classe AgentHelloBye (package: test.agentu) comme type d’agent.

La classe AgentHelloBye implémente un agent qui se limite à afficher le message : « Hello ! i am XXX. » quand son exécution commence; et le message « I was XXX, Bye ! » au moment ou son exécution est arrêtée (avec XXX le nom attribué à l’agent). Si votre programme fonctionne vous devriez donc voir la phrase Hello apparaître sur la console.

Si vous restez sous Eclipse pour tester votre programme, vous ne pourrez pas voir le message Bye, car Eclipse ne permet d’arrêter l’exécution d’un programme que de façon brutale (sans qu’il soit possible que celui-ci puisse réagir au signal d’arrêt). Par contre, si vous exécutez votre programme dans une console système, vous verrez ce message apparaître au moment ou vous allez taper la commande clavier d’arrêt Ctrl-C.

A vous de jouer !

Une fois que vous avez réussi à écrire ce programme, ou si vous n’y parvenez pas, vous pouvez comparez votre code à la proposition de solution ci-dessous.

afficher la solution

b) Mise en pause et relance de l’exécution

Les agents de type AgentHelloBye réagissent aussi quand ils sont mis en pause et relancés. Pour connaitre les messages qu’ils écrivent dans ces moments là, améliorez votre programme précédent de sorte que l’agent soit mis en pause après un délai de 3 secondes, puis relancé 3 secondes plus tard.

Pour gérer plus facilement ces déclenchements d’actions après l’écoulement d’un délai, la librairie SKUAD propose la classe ThreadStack (package: hubudp.util.thread) qui est un utilitaire de manipulation de processus (thread en anglais). Cette classe dispose de la méthode statique suivante :

public static TimerTask perform(Runnable r, long delay)

Elle permet de demander le déclenchement de la méthode run() de l’objet fourni en premier argument, après l’écoulement du délai spécifié en milliseconde dans le deuxième argument.

Runnable est une interface fournie dans l’API Java, et cette interface ne contient qu’une seule méthode :

public void run()

Ainsi, pour réaliser un déclenchement d’action retardé à l’aide de la classe ThreadStack, par exemple l’affichage du mot « Yop » dans la console après un délai de 5 secondes, vous pouvez écrire quelque chose comme ceci :

 ThreadStack.perform(new Runnable({
      public void run(){
           System.out.println("Yop");
      }
 }, 5000);

Dans cette écriture nous avons utilisé une classe anonyme, ce qui est une possibilité offerte par la syntaxe du langage Java. Mais nous pouvons alléger cette écriture grâce à la syntaxe des expressions lambda qui est l’une des nouveauté apportées par Java 8. Dans notre précédent exemple, cela donne ceci :

 ThreadStack.perform(() -> {
     System.out.println("Yop 2");
 }, 5000);

L’expression lambda vient remplacer l’écriture de la classe anonyme par une valeur de type fonction dont la syntaxe générale est :

(...arguments...) -> { ...code... }

Pour notre code « Yop 2 », le compilateur est suffisamment malin pour savoir que le premier argument de la méthode ThreadStack.perform(…) doit être du type Runnable, et il arrive à comprendre que la valeur fonction qu’on lui donne correspond en fait à la méthode run() d’un objet Runnable. Il procède donc à une compilation identique à celle qu’il produirait pour notre premier code « Yop ».

Grâce aux expressions lambda, nous pouvons même faire encore un peu plus court, car les accolades ne sont pas nécessaire dans la mesure ou le code de notre valeur fonction ne contient qu’une seule instruction :

 ThreadStack.perform(() -> System.out.println("Yop 2"), 5000);

Enfin, notez que dans le code d’une valeur fonction on peut utiliser tous les attributs et toutes les méthodes de la classe dans laquelle on se trouve, et tous les arguments et les variables de la méthode dans laquelle on est en train d’écrire. Un peu comme si toutes les instructions que l’on place dans le code de la valeur fonction seraient en fait écrit au même endroit, mais directement en dehors de la fonction (même le this reste celui de la méthode dans laquelle on est).

A vous de jouer !

Une fois que vous avez réussi à écrire ce programme, ou si vous n’y parvenez pas, vous pouvez comparez votre code à la proposition de solution ci-dessous.

afficher la solution

c) Un peu de périodicité

Nous allons améliorer une dernier fois notre programme d’expérimentation. Notre objectif est de produire un code qui va mettre en pause puis relancer l’exécution de l’agent de façon répété.

Dans la version précédente nous nous sommes contenté de reporter l’action de mise en pause et de reprise d’un certain délai. Ces actions ne se sont donc déclenché qu’une seul fois. Ici, il va nous falloir répéter ces actions automatiquement à intervalle de temps régulier (périodiquement) jusqu’à ce que le programme s’arrête.

Cette fois encore la classe ThreadStack (package: hubudp.util.thread) va nous être utile. Et plus particulièrement sa méthode :

public static TimerTask perform(Runnable r, long delay, long periode)

C’est une version surchargée de la méthode de même nom que nous avons utilisé précédemment. Un nouvel argument apparaît dans cette nouvelle version : periode,
et sa valeur donne l’intervalle de temps de répétition de l’action. C’est exactement ce qu’il nous faut pour réaliser cette amélioration. Modifiez donc votre code pour atteindre notre objectif.

Astuces complémentaires :

Cette rubrique comporte une ou plusieurs astuces facultatives qui pourront peut-être vous aider à optimiser l’écriture de votre code. Si vous souhaitez les exploiter, essayez dans un premier temps de produire votre code sans prendre connaissance de ce complément. Puis revenez ensuite jetez un œil dans la suite de cette rubrique.

afficher les astuces
- une seule tâche répétée avec détection de l'action à appliquer

A vous de jouer !

Une fois que vous avez réussi à écrire ce programme, ou si vous n’y parvenez pas, vous pouvez comparez votre code à la proposition de solution ci-dessous.

afficher la solution