-
Notifications
You must be signed in to change notification settings - Fork 12
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
Comments
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. |
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 |
Et à quel moment veux-tu interrompre l'exécution du code python ? |
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. |
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. |
Je pensais plutôt à quelque chose comme un "slot en python", i.e. faire tourner la fonction |
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. |
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... |
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 ? |
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 ! |
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 ? 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. |
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: 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. |
Je viens de remarquer que Sous OSX j'ai de tout façon une erreur:
Qui est semble-t-il liée à multiprocessing. (Voir ici et là) donc je pense que je vais finalement abandonner multiprocessing et revenir à un truc "primaire"... |
Sinon, en truc plus primaire, je viens de penser à |
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.
Dans la fonction message dont tu parles,il est fait référence à Je suis en train de télécharger et essayer ça. Je vous tiens au courant. |
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 ;
|
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. |
Pour 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 $ 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. |
Tu as compilé PythonQt toi même ? Ça sent l'erreur 32-64 bit ça aussi ... |
Pas du tout : une debian testing 32 bits à jour.
C'est aussi ce que j'ai pensé. Je me repencherai là-dessus plus tard. |
Problème diagnostiqué : 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 ? |
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:
Modifier les bonnes variables pour sélectionner python3 et enfin compiler:
Ensuite tu extrais les libs du dossier lib et tu les remplaces de manière adéquate dans patagui. |
T'embête pas : je m'en occuperai… |
Je pense qu'il serait plus simple d'utiliser un |
Deux jours que j'essaye sans succès. Je veux bien que tu le fasses finalement… |
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. |
Ç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… |
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. |
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 ? |
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... |
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. Pour info je bosse dans une nouvelle branche en attendant d'avoir un retour sur #149. |
Pour tuer le processus, faire un appel système me semble le plus simple : |
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 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.
|
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 ! |
Jamais utilisé. Par contre, patacrep fonctionne en python 3.4 (et sans doute 3.5 aussi). Donc pas de problèmes pour essayer. |
(je me permets d'ajouter mon grain de sel) J'ai pas l'impression que asyncio permet d'interrompre une fonction. # 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 |
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. |
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 Du coup l'appel original à la compilation de python est bloquant (et doit être embarqué dans un thread C++). |
Ah bien joué, ça marche ! Merci en tout cas ! |
Tu as une source pour ça ? Je n'ai pas vu où il en était question dans la doc de multiprocessing. |
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. |
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). |
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. |
Connaissant les utilisateurs, c'est une décision plutôt spontanée (clic impulsif sur "Cancel") |
@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 Dis-moi si tu as besoin de plus de précisions. |
@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 |
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. |
@LaTruelle : J'essaye ça à l'occasion… |
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++ ? |
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. |
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. 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. |
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. |
Même en remplaçant "juste" le |
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++… |
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. |
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. |
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.
The text was updated successfully, but these errors were encountered: