patacrep / patagui

Qt4 application for LaTeX songbooks
www.patacrep.com
GNU General Public License v2.0
21 stars 12 forks source link

Stopper l'exécution d'une fonction en python #148

Open LaTruelle opened 9 years ago

LaTruelle commented 9 years ago

L'intégration de patacrep fonctionne maintenant à quelques détails architecturaux près. Je suis par contre confronté à un problème technique en python, je lance donc un appel aux développeurs python du projet !

Une fois la compilation lancée dans python, je ne sais pas comment la stopper depuis le C++. Il me faudrait un système où une fonction soit appelable depuis l'extérieur, et interrompe le déroulement d'une autre fonction. En gros, une espèce de fonction qui simule un Ctrl+C serait parfaite.

Luthaf commented 9 years ago

Comment appelle-tu le code Python ? Avec un appel système, ou en intégrant l'interpréteur ? Dans le premier cas tu peut envoyer un signal au processus, et dans le second lever une exception.

LaTruelle commented 9 years ago

C'est directement en intégrant l'interpréteur. Par contre, pour lancer une exception, c'est depuis le scope de la fonction qui tourne. Ici, je veux stopper la fonction via une autre fonction. À moins que je puisse lancer une exception par un autre biais.

C'est dans ce bout de code, je voudrais interrompre la partie builder.build_steps. Le code est appelé ici

Luthaf commented 9 years ago

Et à quel moment veux-tu interrompre l'exécution du code python ?

LaTruelle commented 9 years ago

Soit à la fin d'une étape (plus propre) soit brutalement, peu importe le résultat. L'avantage de la deuxième solution, c'est qu'un utilisateur peut stopper une compilation quand il le souhaite, si il se rend compte que il manque quelque chose par exemple.

Luthaf commented 9 years ago

En regardand un poil PythonQT, je ne vois pas trop comment prendre la main sur l'interpréteur depuis le C++.

Pour interrompre à la fin d'une étape, le plus simple est peut-être d'émettre un signal et de vérifier une variable dans un coin pour voir si on doit s'arréter après chaque étape.

LaTruelle commented 9 years ago

Je pensais plutôt à quelque chose comme un "slot en python", i.e. faire tourner la fonction build_steps dans une autre thread et faire interrompre depuis le programme principal, mais ce n'est peut être pas possible. Et accessoirement je ne sais pas trop comment se comporteraient des threads dans des threads... En fait, on peut connecter un signal de Qt à une fonction de python (qui sert alors de slot) il faut donc juste un truc en python pur. Sinon effectivement, la version variable vérifiée est le plus simple pour l'instant.

Luthaf commented 9 years ago

Ok j'avais pas compris ça ! En python pour faire des threads tu as soit le module multiprocessing, soit les générateurs. Si tu veux un vrai thread, utilise multiprocessing, les générateurs ne rendent la main que quand ils ont terminé leut traitement.

LaTruelle commented 9 years ago

multiprocessing a vraiment l'air d'être le bon truc ici. Je vais faire quelque tests et voir comment ça se passe entre le multithreading de Qt et celui de Python...

LaTruelle commented 9 years ago

Alors j'ai essayé de le mettre en oeuvre, (73e8f1e75b2d8f54912ff3eabcc8bf0fce31f4e7), et j'ai un souci: en multiprocessing, je ne peux rien imprimer, que ce soit une erreur ou un simple print. De plus, stopper le programme En utilisant le module threading ça fonctionne. Par contre, si j'utilise threading je ne crois pas pouvoir stopper le programme en cours de route, du coup autant faire avec des générateurs je pense, vu que je fais déjà du multithreading via Qt...

Un avis ou un conseil ?

Luthaf commented 9 years ago

C'est quelque chose que je n'ai jamais fait, donc je ne peut pas vraiment aider, désolé. Par contre, la solution m'intéresse !

paternal commented 9 years ago

en multiprocessing, je ne peux rien imprimer, que ce soit une erreur ou un simple print.

Pour être sûr de comprendre : tu as le processus principal (qui entre autres, affiche la fenêtre), et qui lance la compilation dans un autre processus, et tu veux que le processus qui gère la compilation affiche quelque chose dans la fenêtre principale ? C'est ça ? Si c'est ça, il doit y avoir des moyens de communiquer entre différents processus. En python, le module multiprocessing : des queues, de la mémoire partagée, ou des outils encore plus avancés. Et pour des choses encore plus poussées, en utilisant les Lock et Mutex, tu peux faire ce que tu veux.

J'ai la flemme de chercher dans tout ton code où est le problème, mais si tu détailles un peu, je vais essayer de t'aider. Je ne m'y connais pas en interface graphique, mais un peu en multithreading, multiprocessing et compagnie.

LaTruelle commented 9 years ago

Oui c'est ça. Par contre, je pense que c'est lié à pythonqt: quand je lance le code en test en python pur, pas de problème pour imprimer des choses. Quand j'utilise le module threading, ça marche très bien.

Pour les détails d'implémentation: Le process python est appelé depuis Qt via QtConcurrent::run().

Les fonctions "addObject" serve à appeler les objets C++ depuis python, et evalScript évalue le script (!).

La fonction build appelle le process python.

Le problème est que rien ne s'affiche, que ce soit dans stderr ou stdout, ou via la fonction message. En utilisant threading à la place, ça fonctionne très bien par contre. Le mauvais côté de threading est que je ne peux pas stopper l'exécution.

Après avoir cherché un peu sur le site et l'aide de pythonqt, j'ai vu que pythonqt n'est pas thread-safe en général donc c'est peut être de toute façon un peu dangereux. Du coup, je pensais, comme je suis déjà multithreadé via QtConcurrent::run, qu'il vaut mieux utiliser un booléen qui stoppera la fonction build en cours de route.

LaTruelle commented 9 years ago

Je viens de remarquer que Sous OSX j'ai de tout façon une erreur:

The process has forked and you cannot use this CoreFoundation functionality safely. You MUST exec().
Break on __THE_PROCESS_HAS_FORKED_AND_YOU_CANNOT_USE_THIS_COREFOUNDATION_FUNCTIONALITY___YOU_MUST_EXEC__() to debug.

Qui est semble-t-il liée à multiprocessing. (Voir ici et ) donc je pense que je vais finalement abandonner multiprocessing et revenir à un truc "primaire"...

Luthaf commented 9 years ago

Sinon, en truc plus primaire, je viens de penser à subprocess, qui permet en effet d'envoyer un signal au processus lancé, voir de le tuer.

paternal commented 9 years ago

Après avoir cherché un peu sur le site et l'aide de pythonqt, j'ai vu que pythonqt n'est pas thread-safe en général donc c'est peut être de toute façon un peu dangereux.

PythonQT n'est pas thread-safe par défaut, mais en utilisant des verrous (lock, mutex, semaphores), tu peux tout de même utiliser des threads.

Le problème est que rien ne s'affiche, que ce soit dans stderr ou stdout, ou via la fonction message. En utilisant threading à la place, ça fonctionne très bien par contre. Le mauvais côté de threading est que je ne peux pas stopper l'exécution.

Dans la fonction message dont tu parles,il est fait référence à CPPProcess, qui n'est défini nul part (et je n'ai pas l'impression qu'il soit importé par from PythonQT import cppprocess. Est-ce que ça ne serait pas ça le problème ?

Je suis en train de télécharger et essayer ça. Je vous tiens au courant.

LaTruelle commented 9 years ago

Dans la fonction message dont tu parles,il est fait référence à CPPProcess, qui n'est défini nul part (et je n'ai pas l'impression qu'il soit importé par from PythonQT import cppprocess. Est-ce que ça ne serait pas ça le problème ?

CPPProcess vient d'ici. Il est exposé à python via le C++, et correspond au process C++ principal. C'est le mécanisme qui permet d'appeler les fonctions C++ depuis python.

paternal commented 9 years ago

CPPProcess vient d'ici. Il est exposé à python via le C++, et correspond au process C++ principal. C'est le mécanisme qui permet d'appeler les fonctions C++ depuis python.

Ok. Au temps pour moi.

Je n'arrive pas à compiler. Une idée (c'est à jour sur ta branche master ; <PATAGUI> correspond au répertoire du dépôt) ?

# make
[...]
Running make Makefile 
Building 
[  1%] Building CXX object CMakeFiles/songbook-client.dir/src/import-dialog.cc.o
<PATAGUI>/src/import-dialog.cc: In member function ‘int CImportDialog::copy_data(archive*, archive*)’:
<PATAGUI>/src/import-dialog.cc:584:64: error: cannot convert ‘off_t* {aka long int*}’ to ‘int64_t* {aka long long int*}’ for argument ‘4’ to ‘int archive_read_data_block(archive*, const void**, size_t*, int64_t*)’
       int r = archive_read_data_block(ar, &buff, &size, &offset);
                                                                ^
CMakeFiles/songbook-client.dir/build.make:1334: recipe for target 'CMakeFiles/songbook-client.dir/src/import-dialog.cc.o' failed
make[3]: *** [CMakeFiles/songbook-client.dir/src/import-dialog.cc.o] Error 1
CMakeFiles/Makefile2:60: recipe for target 'CMakeFiles/songbook-client.dir/all' failed
make[2]: *** [CMakeFiles/songbook-client.dir/all] Error 2
Makefile:116: recipe for target 'all' failed
make[1]: *** [all] Error 2
Makefile:27: recipe for target 'cmake-build' failed
make: *** [cmake-build] Error 2
LaTruelle commented 9 years ago

C'est bizarre ça bloque sur une fonction que je n'ai pas bougée. Par contre, c'est la branche pythonqt du dépôt patagui qu'il faut prendre, ça devrait aider. Mon master doit être en vrac avec Qt5 et autres changements.

paternal commented 9 years ago

Pour CPPProcess.message, si pythonqt n'est pas thread-safe, c'est normal que ça ne fonctionne pas. Il faut trouver un autre moyen de faire communiquer les deux. Par exemple, le thread principal crée une queue (thread-safe), lance la fonction python, et lit la queue. La fonction python, quand elle veut afficher quelque chose, le met dans la queue. Ainsi, du point de vue de QT, c'est thread-safe, et QT n'est appelé que dans le thread principal. C'est une version simple, où la fonction python ne fait que transmettre des messages à afficher. C'est à adapter pour faire des choses plus complexes.

Comme proposé par @Luthaf, on peut aussi utiliser des multiprocess, avec un même système de queue pour transmettre des info, avec l'avantage qu'un process peut être tué par le processus principal.


Même problème dans la branche pythonqt. Peut-être une différence d'architecture ? Je suis en 32 bits.

$ git branch
  master
* pythonqt
$ make
[...]
Running make Makefile 
Building 
[  1%] Building CXX object CMakeFiles/songbook-client.dir/src/import-dialog.cc.o
<PATAGUI>/src/import-dialog.cc: In member function ‘int CImportDialog::copy_data(archive*, archive*)’:
<PATAGUI>/src/import-dialog.cc:584:66: error: cannot convert ‘off_t* {aka long int*}’ to ‘int64_t* {aka long long int*}’ for argument ‘4’ to ‘int archive_read_data_block(archive*, const void**, size_t*, int64_t*)’
         int r = archive_read_data_block(ar, &buff, &size, &offset);
                                                                  ^
CMakeFiles/songbook-client.dir/build.make:1341: recipe for target 'CMakeFiles/songbook-client.dir/src/import-dialog.cc.o' failed
make[3]: *** [CMakeFiles/songbook-client.dir/src/import-dialog.cc.o] Error 1
CMakeFiles/Makefile2:60: recipe for target 'CMakeFiles/songbook-client.dir/all' failed
make[2]: *** [CMakeFiles/songbook-client.dir/all] Error 2
Makefile:116: recipe for target 'all' failed
make[1]: *** [all] Error 2
Makefile:27: recipe for target 'cmake-build' failed
make: *** [cmake-build] Error 2

J'ai corrigé avec ce patch (je ne sais pas si ça s'exécute correctement, mais ça veut bien compiler). Merci hanwen.

diff --git a/src/import-dialog.cc b/src/import-dialog.cc
index 7cb6387..6b30f98 100644
--- a/src/import-dialog.cc
+++ b/src/import-dialog.cc
@@ -577,7 +577,7 @@ int CImportDialog::copy_data(struct archive *ar, struct archive *aw)
 {   
     const void *buff;
     size_t size;
-    off_t offset;
+    int64_t offset;

     do
     {

Mais j'ai un autre problème plus loin, que j'étudierai un autre jour.

$ make
[...]
Running make Makefile 
Building 
Linking CXX executable songbook-client
../pythonqt/lib/libPythonQt.so: error adding symbols: Fichier dans un mauvais format
collect2: error: ld returned 1 exit status
CMakeFiles/songbook-client.dir/build.make:2672: recipe for target 'songbook-client' failed
make[3]: *** [songbook-client] Error 1
CMakeFiles/Makefile2:60: recipe for target 'CMakeFiles/songbook-client.dir/all' failed
make[2]: *** [CMakeFiles/songbook-client.dir/all] Error 2
Makefile:116: recipe for target 'all' failed
make[1]: *** [all] Error 2
Makefile:27: recipe for target 'cmake-build' failed
make: *** [cmake-build] Error 2

Ça fait des années que je n'ai pas fait de C++, et quand je vois tout ça, ça ne me donne pas envie de m'y remettre… ;)


Au dodo ; la suite au prochain épisode.

Luthaf commented 9 years ago

../pythonqt/lib/libPythonQt.so: error adding symbols: Fichier dans un mauvais format

Tu as compilé PythonQt toi même ? Ça sent l'erreur 32-64 bit ça aussi ...

paternal commented 9 years ago

Tu as compilé PythonQt toi même ?

Pas du tout : une debian testing 32 bits à jour.

Ça sent l'erreur 32-64 bit ça aussi ...

C'est aussi ce que j'ai pensé. Je me repencherai là-dessus plus tard.

paternal commented 9 years ago

Problème diagnostiqué : make va chercher comme bibliothèques QT celles incluses dans le dépôt, qui ont le malheur d'être des bibliothèques pour architecture 64 bits. Mon vieil ordinateur 32 bits ne sait pas quoi en faire.

Je ne comprends rien à l'environnement de compilation. Du coup, @LaTruelle , peux-tu me dire quoi modifier pour que make n'aille pas chercher les bibliothèques (compilées ou non) QT dans le dépôt ?

LaTruelle commented 9 years ago

Ben je dirais qu'il faut compiler pythonqt parce que les dépôts debian ne contiennent qu'une vieille version qui est compilée avec python2 (Le python par défaut) et Qt4. Comme il faut python3 pour patacrep et Qt5 pour le reste de patagui il faut faire ça soi même.

Par conséquent, il faut faire:

svn checkout svn://svn.code.sf.net/p/pythonqt/code/trunk pythonqt-code
cd pythonqt-code

Modifier les bonnes variables pour sélectionner python3 et enfin compiler:

emacs build/python.prf
qmake
make

Ensuite tu extrais les libs du dossier lib et tu les remplaces de manière adéquate dans patagui. Je peux essayer demain sur une machine virtuelle 32 bits, si tu veux, pour fournir les libs adéquates et adapter l'environnement cmake ensuite. Mais ce sera pas immédiat (je crois qu'il faut que je réinstalle une 32 bits qui supporte Qt5, je n'ai qu'une vieille CentOS qui traîne) donc si tu te sens de compiler c'est d'autant mieux !

paternal commented 9 years ago

Je peux essayer demain sur une machine virtuelle 32 bits

T'embête pas : je m'en occuperai…

Luthaf commented 9 years ago

Je pense qu'il serait plus simple d'utiliser un ExternalProject de CMake pour compiler PythonQt. Sinon on s'expose à des errerurs de compatibilité ABI entre compilateurs C++, en plus des erreurs comme celle ci. Je peut y jeter un oeil un de ces quatres au besoin.

paternal commented 9 years ago

Je peux essayer demain sur une machine virtuelle 32 bits

T'embête pas : je m'en occuperai…

Deux jours que j'essaye sans succès. Je veux bien que tu le fasses finalement…

LaTruelle commented 9 years ago

OK, j'ai envoyé les librairies 32 bits sur le dépôt (7926289dcceef50705cd4fedde0b4796a3f07b5a). Il faut les remplacer à la main, c'est très sale mais ça devrait aller pour tester.

C'est compilé avec Qt5.4.2 sur CentOS 6.6 32bits. Si ça marche pas, donne moi ta config, je ferai une machine virtuelle Debian demain.

paternal commented 9 years ago

Ça marche enfin ! Merci. Je vais enfin pouvoir m'attaquer au problème d'origine de ce fil…

HS : Je rejoins le commentaire de @Luthaf je ne sais plus où : analyser le dossier utilisateur à la recherche de chansons à chaque lancement du programme, c'est mal. Sur mon ordinateur, cette recherche prend cinq minutes (sans exagérer), pendant lesquelles je peux difficilement utiliser d'autres applications…

LaTruelle commented 9 years ago

Oui l'analyse c'est clair c'est pourri. Ça vient (je pense) de l'ancienne partie parce que je n'ai touché à rien de ce point de vue là. Par contre j'ai du mal à le reproduire... mais je vais essayer. Quitte à faire ça en machine virtuelle.

paternal commented 9 years ago

Où en es-tu de ce problème, @LaTruelle ? J'avoue que de mon côté je n'ai pas avancé : le fait que ce soit du C++ me motive peu (je n'y ai pas touché depuis des années, et ça me me manque pas), et les cinq minutes d'analyse au lancement découragent aussi… Tu as toujours besoin d'aide ?

LaTruelle commented 9 years ago

De retour après un déménagement, du coup je vais m'y relancer un peu plus sérieusement. J'ai un External Project (https://github.com/LaTruelle/patagui/tree/pythonqt-externalproject) qui devrait fonctionner en multiplateforme, je vais le merger sous peu dans le fork pour pouvoir compiler PythonQt proprement.

Je suis en train de bosser en parallèle sur le problème de l'analyse du datadir, pour éviter l'analyse brutale. Pour le sujet original de l'issue, du coup pas avancé, mais j'imagine que quand le build sera plus facile, de même que les tests ça ira mieux...

LaTruelle commented 9 years ago

Alors je me relance dans cette issue. Je me suis rendu compte que si on utilise multiprocessing, on aura un problème sous Windows lié au mode de lancement des nouveaux process (la méthode spawn ne conserve pas les objets, donc il faut les transférer et c'est un peu le bordel...), donc je vais revenir à des choses plus primaires pour commencer et voir ensuite. Je penche pour du threading, soit directement dans le C++ si ça marche (PythonQt est un peu capricieux pour ça), soit en utilisant le module python threading. Il restera les problèmes de communication entre process mais ça devrait finir par se résoudre... Une idée, @Luthaf @paternal ?

Pour info je bosse dans une nouvelle branche en attendant d'avoir un retour sur #149.

paternal commented 9 years ago

Pour tuer le processus, faire un appel système me semble le plus simple : python -m patacrep.songbook carnet.sb. Par contre, avec ça, tu en es réduit à interpréter la sortie pour repérer les erreurs, et tu ne profiteras pas de patacrep/patacrep#121.

LaTruelle commented 9 years ago

Haha, dans cette branche j'arrive à avoir le programme qui répond, et à ce que le thread qui construit le songbook voie un flag en cours de route. Du coup je peux arrêter le build entre deux étapes. Problème, je ne dois pas le faire comme il faut, vu que l'ensemble crashe dès que le thread est arrêté. Le script python est ici en particulier les deux dernières fonctions. stopBuild est appelé de l'extérieur du thread dans lequel buildSongbook tourne. Je ne suis pas vraiment bon en multithreading du coup c'est plutôt de la bidouille...

paternal commented 9 years ago

Le souci avec l'arrêt de la compilation entre deux étapes est que les étapes peuvent être très longue. Par exemple, la compilation de l'ensemble de patadat prend chez moi plusieurs minutes. Cliquer sur « Arrêter » et devoir attendre deux minutes avant de reprendre la main n'est pas ce qu'attend l'utilisateur.

LaTruelle commented 9 years ago

Et tu penses quoi de l'utilisation d'asyncio ? Ça nécessite d'être en 3.4 ou 3.5 mais bon... si ça résout nos problèmes !

paternal commented 9 years ago

Jamais utilisé. Par contre, patacrep fonctionne en python 3.4 (et sans doute 3.5 aussi). Donc pas de problèmes pour essayer.

oliverpool commented 9 years ago

(je me permets d'ajouter mon grain de sel)

J'ai pas l'impression que asyncio permet d'interrompre une fonction. En revanche je pense que le 2ème exemple de @paternal peut être aisément adapté à songbook.py :

# Define global variables
process = None
stopProcess = False

def build():
    global stopProcess
    global process
    stopProcess = False
    steps = 10
    process = threading.Thread(target=buildSongbook, args=(steps,))
    try:
        #logger.info('Starting process')
        process.start()
    except threading.ThreadError as error:
        #logger.warning('process Error occured')
        message(error)

    period = 2
    while process.is_alive():
        print(".")
        if stopProcess:
            process.terminate()
            print("terminated")
        # Check in 2 seconds
        process.join(period)
    print("end build")

def message(text):
    print("Msg: " + text)

def buildSongbook(steps):
    print("-start-")
    time.sleep(steps)
    print("-end-")

def stopBuild():
    message("Terminating process")
    global stopProcess
    stopProcess = True

Tant que la compilation tourne, mon programme vérifie toutes les 2 secondes l'état de stopProcess. Si celui-ci passe à False, le process est arrêté.

LaTruelle commented 9 years ago

Hello,

J'ai testé, mais ça ne marche pas: l'appel à stopBuild() crashe le programme. (dans un délai inférieur à deux secondes du coup, donc formellement ça fait le boulot mais bon...)

Est-ce que cela peut être lié au fait que pythonQt n'est pas thread-safe ? Je lance la compilation en utilisant QtConcurrent::run, cela ajouté au module threading ça rend peut être les choses compliquées...

C'est dans 6a219d3b736a9ca6deadad8633c98776f119a55b si vous voulez tester en grandeur nature.

oliverpool commented 9 years ago

Effectivement, je pense qu'appeler le script même script python depuis deux thread est dangereux.

J'ai eu une autre idée: maintenir l'état de la compilation (stop ou encore) dans le C++ (et pas dans python)

A la ligne 102, au lieu de vérifier une variable interne à Python, tu fais un appel à une fonction dans Qt (style continue_compilation()). En fonction du résultat, le process est interrompu ou non. Du coup la gestion "Thread safe" est reportée à Qt pour les accès à cette variable stopProcess.

Du coup l'appel original à la compilation de python est bloquant (et doit être embarqué dans un thread C++).

LaTruelle commented 9 years ago

Ah bien joué, ça marche ! Il a juste fallu que je fasse appel à un vieux hack: on ne peut pas terminer un thread (thread.terminate() n'existe pas). Par contre, si tu appelles une fonction non-existante et que tu lève un AttributeError, tu plante l'interpréteur, et donc la thread s'arrête. C'est très très sale d'un point de vue élégance de programmation, mais bon, au moins ça fait le boulot ! Je vais le merger dans la pull-request principale après un peu de nettoyage.

Merci en tout cas !

paternal commented 9 years ago

Alors je me relance dans cette issue. Je me suis rendu compte que si on utilise multiprocessing, on aura un problème sous Windows lié au mode de lancement des nouveaux process (la méthode spawn ne conserve pas les objets, donc il faut les transférer et c'est un peu le bordel...),

Tu as une source pour ça ? Je n'ai pas vu où il en était question dans la doc de multiprocessing.

LaTruelle commented 9 years ago

Dans la doc, ils disent que spawn est la seule méthode utilisable dans windows. Quand j'ai fait mes tests en utilisant "spawn" comme méthode sur Unix, le redémarrage d'un nouvel interpréteur faisait plus ou moins planter pythonQt, je pense parce que python est perdu pour savoir quels objets il doit transférer ou pas. Après je n'ai pas fait de tests extensifs... vu que je n'ai toujours pas réussi à compiler le tout sous windows.

paternal commented 9 years ago

la méthode spawn ne conserve pas les objets,

J'ai l'impression qu'elle ne conserve pas les objets globaux, mais que les objets passés en argument sont conservés. Après, je ne sais pas comment faire pour obtenir les valeurs de retour de la fonction.

EDIT : En utilisant des Pipe ou des Queues (tels que définis dans le module multiprocessing), ça doit fonctionner. Reste à résoudre ton problème (ne pas faire planter tout PythonQT au moment de lancer la compilation).

J'essayerai peut-être de voir ce que ça donne avec des multiprocessing à l'occasion (mais pas forcément très vite : j'ai une todo liste longue comme le bras et un bébé à la maison).

paternal commented 9 years ago

Une proposition avec multiprocessing.

import time
import multiprocessing

def longue(*args):
    time.sleep(5)
    return sum(args)

class ProcessWithReturnValue(multiprocessing.Process):
    def __init__(self, target, args=(), kwargs={}, *class_args, **class_kwargs):
        class_kwargs.update({
            'target': self._queue_returnvalue,
            'args': (target, ),
            'kwargs': {'args': args, 'kwargs': kwargs},
            })
        self._returnqueue = multiprocessing.Queue()
        super().__init__(*class_args, **class_kwargs)

    def _queue_returnvalue(self, target, args=(), kwargs={}):
        self._returnqueue.put(target(*args, **kwargs))

    @property
    def returnvalue(self):
        if self.is_alive():
            raise multiprocessing.ProcessError("Wait for the process to terminate!")
        return self._returnqueue.get()

def main():
    timeout = float(input("Interrompre la fonction après combien de secondes? "))
    p = ProcessWithReturnValue(
        target=longue,
        args= ([1, 2, 3]),
    )
    p.start()

    time.sleep(timeout)
    if p.is_alive():
        p.terminate()
        print("Function cancelled.")
    else:
        if p.exitcode != 0:
            print("Function failed…")
        else:
            print("Function returned '{}'.".format(p.returnvalue))

if __name__ == "__main__":
    main()

On a une fonction longue (qui met cinq secondes à s'exécuter). On demande à l'utilisateur au bout de combien de temps stopper la fonction (timeout), et c'est parti. Si à la fin du timeout, la fonction est finie, on récupère et on affiche le résultat ; si elle n'est pas terminée, on la tue.

Si @LaTruelle me montre à quel endroit ça doit aller dans le code patagui, je peux essayer de voir si j'arrive à intégrer ça.

oliverpool commented 9 years ago

On demande à l'utilisateur au bout de combien de temps stopper la fonction (timeout),

Connaissant les utilisateurs, c'est une décision plutôt spontanée (clic impulsif sur "Cancel")

LaTruelle commented 9 years ago

@paternal tu peux essayer de l'intégrer dans cette branche.

Il faut mettre le code python dans songbook.py. La fonction build est celle qui est appelée par le code C++ pour lancer la compilation. La fonction buildSongbook fait effectivement le boulot.

En l'état actuel des choses, le build est arrêté via un flag qui est lu dans le C++ mais on peut changer ça. Il suffit d'aller dans patacrep.cc et de changer la ligne en pythonModule.evalScript("taFonctionPythonQuiArreteLeBuild()");

Dis-moi si tu as besoin de plus de précisions.

oliverpool commented 9 years ago

@paternal & @LaTruelle : je pense qu'il est possible de combiner les deux approches.

Utiliser un flag externe (pour déclencher l'interruption depuis Qt) et multiprocessing pour terminer cela proprement.

paternal commented 9 years ago

Connaissant les utilisateurs, c'est une décision plutôt spontanée (clic impulsif sur "Cancel")

Oui, ça sera fait avec des threads etc. mais ça ne changera pas grand'chose à mon exemple : c'était juste pour montrer comment on pouvait faire pour annuler une fonction, exécuter une fonction en récupérant sa valeur de retour.

paternal commented 9 years ago

@LaTruelle : J'essaye ça à l'occasion…