Je me suis tiré les cheveux pour essayer de comprendre celui-ci, en espérant que quelqu'un d'autre l'a déjà rencontré et sait comment le résoudre :)
J'essaie de créer un point de terminaison Flask très simple qui doit simplement appeler un script de blocage php
long (pensez à while true {...}
). J'ai essayé différentes méthodes pour lancer le script de manière asynchrone, mais le problème est que mon navigateur ne reçoit jamais la réponse, même si le code permettant de générer la réponse après l'exécution du script est exécuté.
J'ai essayé d'utiliser multiprocessing
et threading
, ni l'un ni l'autre ne semble fonctionner:
# multiprocessing attempt
@app.route('/endpoint')
def endpoint():
def worker():
subprocess.Popen('Nohup php script.php &', Shell=True, preexec_fn=os.setpgrp)
p = multiprocessing.Process(target=worker)
print '111111'
p.start()
print '222222'
return json.dumps({
'success': True
})
# threading attempt
@app.route('/endpoint')
def endpoint():
def thread_func():
subprocess.Popen('Nohup php script.php &', Shell=True, preexec_fn=os.setpgrp)
t = threading.Thread(target=thread_func)
print '111111'
t.start()
print '222222'
return json.dumps({
'success': True
})
Dans les deux scénarios, je vois les 111111
et 222222
, mais mon navigateur se bloque toujours sur la réponse du noeud final. J'ai essayé p.daemon = True
pour le processus, ainsi que p.terminate()
mais pas de chance. J'espérais que lancer un script avec Nohup dans un autre shell et que des processus/threads distincts fonctionneraient, mais Flask ou uWSGI en est affecté.
Étant donné que cela fonctionne localement sur mon Mac lorsque je lance mon application Flask directement avec python app.py
et que je le frappe directement sans passer par mon proxy Nginx et mon interface uWSGI, je commence à croire que ce n'est peut-être pas le code qui pose problème. Et comme mon Nginx ne fait que transmettre la demande à uWSGI, je pense que c'est peut-être quelque chose qui la cause.
Voici ma configuration ini pour le domaine pour uWSGI, que je cours en mode empereur:
[uwsgi]
protocol = uwsgi
max-requests = 5000
chmod-socket = 660
master = True
vacuum = True
enable-threads = True
auto-procname = True
procname-prefix = michael-
chdir = /srv/www/mysite.com
module = app
callable = app
socket = /tmp/mysite.com.sock
Ce genre de choses est le cas d'utilisation réel et probablement principal de Python Celery
( http://www.celeryproject.org/ ). En règle générale, n'exécutez pas de travaux longs liés au processeur dans le processus wsgi
. C'est délicat, c'est inefficace, et le plus important, c'est plus compliqué que de configurer une tâche {asynchrone} dans un céleri. Si vous souhaitez simplement créer un prototype, vous pouvez définir le courtier sur memory
et ne pas utiliser de serveur externe, ou exécuter une seule unité d'exécution redis
sur le même ordinateur.
De cette façon, vous pouvez lancer la tâche, appelez task.result()
qui bloque, mais elle bloque de manière liée à IO, ou mieux encore, vous pouvez simplement revenir immédiatement en récupérant le task_id
et créer un second point de terminaison/result?task_id=<task_id>
qui vérifie si le résultat est disponible:
result = AsyncResult(task_id, app=app)
if result.state == "SUCCESS":
return result.get()
else:
return result.state # or do something else depending on the state
De cette façon, vous avez une application wsgi
non bloquante qui fait ce qui convient le mieux: des appels courts non liés à l'UC qui ont IO au maximum avec une planification au niveau du système d'exploitation, puis vous pouvez vous fier directement à la wsgi
server workers|processes|threads
ou tout ce dont vous avez besoin pour adapter l'API dans un serveur wsgi comme uwsgi, gunicorn, etc. pour 99% des charges de travail lorsque le céleri évolue horizontalement en augmentant le nombre de processus de travail.
Cette approche fonctionne pour moi, elle appelle la commande de dépassement de délai (sommeil 10s) dans la ligne de commande et la laisse fonctionner en arrière-plan. Il retourne la réponse immédiatement.
@app.route('/endpoint1')
def endpoint1():
subprocess.Popen('timeout 10', Shell=True)
return 'success1'
Cependant, ne testez pas sur le serveur WSGI, mais simplement localement.