Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
LaTruelle opened this issue Jun 9, 2015 · 59 comments
Open

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

LaTruelle opened this issue Jun 9, 2015 · 59 comments

Comments

@LaTruelle
Copy link
Contributor

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
Copy link
Contributor

Luthaf commented Jun 9, 2015

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
Copy link
Contributor Author

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
Copy link
Contributor

Luthaf commented Jun 9, 2015

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

@LaTruelle
Copy link
Contributor Author

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
Copy link
Contributor

Luthaf commented Jun 9, 2015

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
Copy link
Contributor Author

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
Copy link
Contributor

Luthaf commented Jun 9, 2015

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
Copy link
Contributor Author

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
Copy link
Contributor Author

Alors j'ai essayé de le mettre en oeuvre, (73e8f1e), 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
Copy link
Contributor

Luthaf commented Jun 23, 2015

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
Copy link
Contributor

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
Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link
Contributor

Luthaf commented Jun 29, 2015

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
Copy link
Contributor

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
Copy link
Contributor Author

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
Copy link
Contributor

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
Copy link
Contributor Author

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
Copy link
Contributor

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
Copy link
Contributor

Luthaf commented Jun 29, 2015

../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
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor Author

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
Copy link
Contributor

Je peux essayer demain sur une machine virtuelle 32 bits

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

@Luthaf
Copy link
Contributor

Luthaf commented Jun 30, 2015

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
Copy link
Contributor

paternal commented Jul 1, 2015

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
Copy link
Contributor Author

OK, j'ai envoyé les librairies 32 bits sur le dépôt (7926289). 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
Copy link
Contributor

paternal commented Jul 2, 2015

Ç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
Copy link
Contributor Author

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
Copy link
Contributor

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
Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link
Contributor

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
Copy link
Contributor Author

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
Copy link
Contributor

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
Copy link
Contributor Author

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
Copy link
Contributor

paternal commented Nov 1, 2015

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

@oliverpool
Copy link

(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
Copy link
Contributor Author

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 6a219d3 si vous voulez tester en grandeur nature.

@oliverpool
Copy link

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
Copy link
Contributor Author

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
Copy link
Contributor

paternal commented Nov 2, 2015

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
Copy link
Contributor Author

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
Copy link
Contributor

paternal commented Nov 2, 2015

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
Copy link
Contributor

paternal commented Nov 3, 2015

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
Copy link

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
Copy link
Contributor Author

@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
Copy link

@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
Copy link
Contributor

paternal commented Nov 3, 2015

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
Copy link
Contributor

paternal commented Nov 3, 2015

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

@paternal
Copy link
Contributor

paternal commented Nov 8, 2015

Pour info, j'ai commencé à bosser là-dessus, et ça avance…

Je me suis posé une question : Y a-t-il une raison pour laquelle on cherche à arrêter une fonction en python plutôt qu'une fonction en C++ ?

@LaTruelle
Copy link
Contributor Author

Parce que la façon simple de faire du multithreading avec Qt c'est via QtConcurrent::run() et qu'on ne peut pas l'arrêter en cours de route. Après ce n'est pas définitif comme remarque, j'ai cherché à utiliser d'autres options, mais si on a une version simple via python c'est plus facile à gérer, entre autres parce que Python est dans sa propre thread et donc la communication entre les process (en particulier entre le GUI et l'interpréter Python) reste simple.

@oliverpool
Copy link

Y a-t-il une raison pour laquelle on cherche à arrêter une fonction en python plutôt qu'une fonction en C++ ?

Le signal de départ est donné par C++ et la compilation s'effectue de manière bloquante (dans un thread séparé de Qt, donc non-bloquant pour l'interface): l'interpréteur python ne "rend la main" que lorsque tous ses thread/process sont terminés.
Le signal d'arrêt est aussi reçu par C++.
La communication de l'arrêt à l'intépréteur python via un 2nd thread C++ (le 1er étant "bloqué") fait complètement planter python (ce qui arrête notamment la compilation ^^).

La suggestion que j'ai proposé, est que l'appel "bloquant" de python vérifie de lui-même le signal (via un appel C++) et fasse du multithreading/-processing (bloquant du point de vue de C++: tant que interpréteur python tourne, le thread C++ doit attendre) qui lui permette de vérifier si il y a eu une demande d'arrêt de manière régulière.


Bref, à mon avis le principal soucis réside dans la communication concurrente (2 thread C++) à un interpréteur python unique.

@paternal
Copy link
Contributor

C'est bien plus compliqué que je ne pensais : quoi que je fasse, je me heurte à un segfault quand j'essaie de tuer ma fonction. J'ai essayé deux versions, sans succès, avec multiprocessing et subprocess. J'abandonne.

@oliverpool
Copy link

Même en remplaçant "juste" le threading par du multiprocessing dans le code actuel ?

@paternal
Copy link
Contributor

Je ne crois pas (mais j'ai fait un pull, et je ne peux plus compiler, et je n'arrive pas à remettre la main sur une version ou ça compile). Ça ne me motive pat à me remettre au c++…

@LaTruelle
Copy link
Contributor Author

Ah mince j'ai changé des choses dans la partie compilation de pythonqt pour pouvoir compiler sur appveyor et travis. Je teste ça sur debian et je te dis. Je pense que sinon, tu dois pouvoir faire un checkout de 12917b8 et ça doit fonctionner.

@LaTruelle
Copy link
Contributor Author

Sinon, pour le changement "brutal" threading->multiprocessing j'avais essayé dans des commits anciens et ça ne marchait pas vraiment. En tout cas, on décalait le problème plus qu'on améliorait les choses.

@LaTruelle
Copy link
Contributor Author

@paternal C'est résolu dans be4f336, j'avais un peu trop nettoyé les sources de PythonQt, désolé.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants