Outils pour utilisateurs

Outils du site


public:interruption_calcul

Interruption des calculs

Cette page décrit les spécificités de l'interruption des calculs de TXM. Ticket #257

Objectif

Afin d'avoir un meilleur contrôle de l'application, l'utilisateur doit être capable d'annuler des tâches en cours d'exécution. Cela peut être par exemple l'export de donnés où encore un calcul sur un corpus.

Certaines tâches ne peuvent pas être interrompues brutalement sans compromettre la stabilité de l'application. L'écriture dans un fichier est concerné par exemple. Il faut prévoir un mécanisme de protection afin d'exclure l'interruption par moment.

Lorsqu'une tâche est interrompue il faut qu'elle se termine proprement. Par exemple ses ressources doivent être libérées.

Méthode

Protocole de conception

Voici le protocole de conception de la solution:

Avancement

23/08/13: Les Jobhandler non initialisés pas la méthode “runInit” ne peuvent pas être interrompus. Cela peut être le cas de tâches qui ne sont pas issus du développeur (par exemple une sous-tâche exécutant un script Groovy). Ainsi on évite d'avoir une exception “ThreadDeath”.

21/08/13: L'interruption de l'import de données et des scripts Groovy est possible. Cependant elle est “brutale” car les scripts sont externes à l'application. Il n'y a pas de nettoyage de données et certaine phases peuvent être critiques.

16/07/13: La méthode est déployée sur l'ensemble de la RCP. Il n'est pas encore possible d'interrompre l'import de données

11/07/13: Les méthodes d'export de données et de calcul de concurrences sont interruptibles.

09/07/13: La méthode d'interruption est définie. Elle est détaillée dans la partie “Interruption des threads sous Eclipse RCP” ci-dessous. On passe à la partie test. Voir plus bas pour le protocole.

Solution

Documentation : interruption des threads sous Eclipse RCP

Cette partie synthétise les différentes méthodes testées pour pouvoir gérer l'interruption des threads.

Job et JobHandler

TXM utilise l'API Job d'Eclipse. Pour l'utiliser on instancie la classe JobHandler et on redéfinit la méthode “run”. D'un point de vue technique les Jobs sont basés sur les threads. La gestion de ces derniers est transparente pour le programmeur.

Interruption

Tester l'interface Monitor

Un job est associé à l'interface “Monitor”. Quand l'utilisateur appuie sur “Annuler” depuis la GUI, un flag est mis à “true”. Il suffit de le tester régulièrement dans le “JobHandler” avec “if (monitor.isCanceled()) return Status.CANCEL_STATUS;”

Cette méthode était utilisée à l'origine. L’inconvénient est la redondance du test “isCanceled()”.

Tester l'interruption du thread

L'objectif est de récupérer le thread associé au job et de l'interrompre.

On ajoute au “JobHandler” un attribut “myThread” qui correspond au thread exécutant le Job. Dans la méthode “run” on lui affecte le thread courant avec “this.myThread = Thread.currentThread()”. On redéfinit également la méthode “canceling()”. Elle est appelée quand l'utilisateur appuie sur “Annuler”. Pour interrompre le thread on appelle la méthode “this.myThread.interrupt()”.

On rencontre le même problème que la méthode précédente. Le thread n'est pas réellement interrompu, son flag “isCanceled” passe à “true”. C'est au programmeur de tester son interruption avec “if (this.myThread.isInterrupted()) return Status.CANCEL_STATUS;”

Cette méthode ne présente pas d'amélioration, la gestion de l'interruption est seulement différente.

Stopper le thread: méthode utilisée

On se base sur la méthode “Thread.stop()”. Elle est dépréciée car elle provoque l'interruption brutale du thread et peut rendre le système instable. Voir cette page pour plus d'informations: Javadoc: primitives thread dépréciées.

Cette méthode a le mérite de fonctionner. Il faut trouver un moyen de la sécuriser en permettant la définition de zones d'exclusion d'interruption. Par exemple lors de l'écriture d'un fichier.

Cette méthode reprend la précédente en ajoutant un sémaphore dans le “JobHandler”. Quand on est en zone critique on le prend. Lors d'un appel à “canceling()” on le prend avant d'appeler “this.myThread.stop()”.

Lorsqu'un thread est stoppé, une exception “ThreadDeath” est générée qu'il faut gérer. Autrement un message d'erreur s'affiche avec le contenu de la stacktrace. Il est alors impératif d'encapsuler le contenu de la méthode “run” dans un bloc “try{} catch(ThreadDeath t) {}”. Lors de l'appel à “Thread.stop()” le contenu du bloc catch est exécuté. On peut alors nettoyer les variables utilisées.

On retient cette méthode pour l'interruption des calculs. La gestion des sémaphores s'effectuera au niveau de la Toolbox.

Modification de la classe JobHandler

Protection des JobHandler systèmes

Il faut prévoir que certains JobHandler ne sont pas issus du programmeur, mais de la plate-forme Eclipse par exemple. C'est la cas notamment lors de l'exécution de scripts Groovy. Dans le code on utilise un JobHandler pour lancer la commande, mais son exécution ne dépend pas du programmeur. La plate-forme peut très bien la gérer par plusieurs threads.

Ajout d'attributs

Pour être interrompu le JobHandler doit avoir:

  • une référence sur le thread l'exécutant: Thread myThread
  • un sémaphore pour se protéger de l'interruption en zone critique: Semaphore mySemaphore
  • un booléen indiquant que le thread est issu du programmeur et qu'il peut être interrompu: boolean canBeInterrupted = false (par défaut)

Méthode d'initialisation: runInit

Cette méthode est appelée au début de la méthode “run” du JobHandler. Elle prend en paramètre l'objet “monitor” fournit à la méthode “run”.

Elle permet d'associer le JobHandler à son thread, d'initialiser le sémaphore et mettre le booléen d'interruption à “true”.

Protection des zones critiques

Un système de sémaphore permet de gérer les zones critiques du JobHandler (exemple: écriture dans un fichier).

Pour empêcher l'interruption on appelle:

  • this.acquireSemaphore() ou this.tryAcquireSemaphore()

Pour être interruptible à nouveau:

  • this.releaseSemaphore()

La différence entre acquireSemaphore et tryAcquireSemaphore est que la dernière est non bloquante.

Méthode d'interruption

La méthode “canceling” fournit par la classe JobHandler est redéfinit (elle ne fait rien par défaut).

  • Test de la valeur du booléen d'interruption
  • Si vraie
    • Acquérir le sémaphore pour éviter les conflits
    • Lancer myThread.stop()

Stockage de résultats

Un jobHandler s’exécute dans le cadre du scheduler d'Eclipse et est donc Asynchrone. Pour attendre la fin de son travail, il faut appeler la méthode “join” du JobHandler.

On peut vérifier le status de sortie du JobHandler avec la méthode getResult :

if (job.getResult() == Status.OK_STATUS) { // ou Status.CANCEL.STATUS

} 

Enfin on rajoute la méthode “getResultObject()” que la JobHandler doit implémenter pour retourner le résultat du travail et faire quelque chose avec :

if (job.getResult() == Status.OK_STATUS) { // ou Status.CANCEL.STATUS
	MonResultat result = (MonResultat) job.getResultObject(); // non null
	// ...
} 

Principe général

Cette partie décrit l'utilisation de la méthode.

On se situe dans un JobHandler. Pour pouvoir l'interrompre il faut:

  • Appeler this.runInit(monitor) au lancement de la méthode “run”
  • Encapsuler l'ensemble des opérations dans un bloc try / catch
    • Catch un “ThreadDeath”
    • Effectuer des opérations de nettoyage (optionnel: par exemple fermer un fichier)
    • Retourner “Status.CANCEL_STATUS”

Exemple

Voici un exemple de thread interruptible au niveau de la RCP. Les lignes comportant des “< - -” sont essentielles.

JobHandler jobhandler = new JobHandler(NLS.bind(Messages.CooccurrencesEditor_12, corpus)) {
  @Override
  protected IStatus run(IProgressMonitor monitor) {
    this.runInit(monitor); <--
    JobsTimer.start();
    monitor.beginTask(Messages.CooccurrencesEditor_13, 100);
    try {
      ...
    } catch (ThreadDeath td) { <--
      // clean
      return Status.CANCEL_STATUS; <--
    } catch (Exception e) {
      e.printStackTrace();
      return Status.CANCEL_STATUS;
    }
    monitor.done();
    JobsTimer.stopAndPrint();
    return Status.OK_STATUS;
  }
};

jobhandler.startJob();  

Implémentation des interruptions au niveau de la TBX

La classe de la TBX doit hériter de la classe “Function” et implémenter la méthode “clean”. Depuis la RCP dans le JobHandler, on reprend l'exemple donné précédemment et on passe le JobHandler à la classe de la TBX au début du “run” comme ceci:

  • ( (Function) f).setCurrentMonitor(this)

Puis dans le catch ThreadDeath on appelle la fonction de clean:

  • (Function) f).clean()

Les méthodes de protection des interruptions sont les mêmes:

  • this.acquireSemaphore() ou this.tryAcquireSemaphore()
  • this.releaseSemaphore()

Liste des tâches protégées de l'interruption

Voici la liste des classe possédant (un/des) JobHandler protégé(s) en partie de l'interruption, ainsi que la raison:

  • RCP
    • org.txm.rcpapplication.commands
      • ExecuteScriptImport → redémarrage de la TBX
    • org.txm.rcpapplication.commands.base
      • SendToR → envoie de données à R
    • org.txm.rcpapplication.commands.function
      • ComputeLexicalTable → storeResult
      • ComputeSpecifities → idem
    • org.txm.rcpapplication.commands.workspace
      • AddBase → redémarrage de la TBX
      • ConvertCorpus → idem
      • PurgeCorpora → interaction avec la TBX (createEmptyWorkspace)
    • org.txm.rcpapplication.editors
      • CAHEditor → stepCompute / storeResult
    • org.txm.rcpapplication.editors.coocurrences
      • CoocurencesEditor → storeResult
    • org.txm.rcpapplication.editors.queryindex
      • QueryIndexEditor → storeResult
    • org.txm.rcpapplication.editors.references
      • ReferencerEditor → storeResult
    • org.txm.rcpapplication/editors.vocabulary
      • VocabularyEditor → remove / storeResult
  • TBX
    • org.txm.functions.concordance
      • Concordance → comparator.initialize()
    • org.txm.functions.QueryIndex
      • QueryIndex → ajout et suppression de ligne
    • org.txm.functions.Vocabulary
      • Vocabulary → cut()

Liste des tâches non interruptibles

Voici la liste des tâches non interruptibles. La solution n'a pas été déployée pour elles car elles ne sont pas appelées par l'utilisateur même.

  • RCP
    • org.txm.rcpapplication.commands
      • RestartTXM
    • org.txm.rcpapplication
      • ApplicationWorkbench −> postInit(), initialisation de la TBX
    • org.txm.rcpapplication.commands.R
      • Submit → les commandes passées à R

Protocole de test

Voici le protocole:

  • Prendre une fonctionnalité “critique” (utilisation de fichier, socket…)
  • Définir les zones d'exclusion d'interruption avec un sémaphore
  • Gérer le nettoyage du thread (fermeture d'un fichier…)
  • Observer le comportement lors d'une interruption (temps d'attente, libération des ressources…)
  • Bilan

Bilan

La solution est fonctionnelle. Il est possible d'interrompre proprement une tâche, le nettoyage des données est géré par le programmeur.

L'exclusion d'interruption est satisfaisante. C'est également au programmeur de déterminer les zones critiques à protéger.

État de la plate-forme

La solution est déployée à l'ensemble de l'application, elle concerne également l'import de données et les scripts Groovy. En revanche l'interruption est “brutale”. Pour pouvoir utiliser le système d'exclusion d'interruption et de nettoyage de données, il faudrait développer une interface entre TXM et les scripts Groovy.

Recette

NK: la méthode est fonctionnelle sous Linux x64

public/interruption_calcul.txt · Dernière modification : 25/10/2016 11:59 de matthieu.decorde@ens-lyon.fr