webentwicklung-frage-antwort-db.com.de

Langes Skript vom Endpunkt der Flasche

Ich habe mir die Haare ausgezogen und versucht, das herauszufinden, in der Hoffnung, dass jemand anderes dies bereits erlebt hat und wissen kann, wie man es löst :)

Ich versuche, einen sehr einfachen Flask-Endpunkt zu erstellen, der nur ein lang laufendes blockierendes php-Skript (think while true {...}) aufrufen muss. Ich habe ein paar verschiedene Methoden ausprobiert, um das Skript asynchron zu starten. Das Problem ist jedoch, dass mein Browser die Antwort nie zurückerhält, obwohl der Code zum Generieren der Antwort nach dem Ausführen des Skripts ausgeführt wird.

Ich habe versucht, sowohl multiprocessing als auch threading zu verwenden. Beide scheinen nicht zu funktionieren:

# 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
  })

In beiden Szenarien sehe ich 111111 und 222222, aber mein Browser hängt immer noch von der Antwort des Endpunkts ab. Ich habe p.daemon = True für den Prozess sowie p.terminate() versucht, aber kein Glück. Ich hatte gehofft, ein Skript mit Nohup in einer anderen Shell zu starten, und separate Prozesse/Threads würden einfach funktionieren, aber irgendwie ist Flask oder uWSGI davon betroffen.

Aktualisieren

Da dies lokal auf meinem Mac funktioniert, wenn ich meine Flask-App direkt mit python app.py starte und direkt klicke, ohne meinen Nginx-Proxy und meine uWSGI zu durchlaufen, gehe ich langsam davon aus, dass es nicht der Code selbst ist, der Probleme hat. Und weil mein Nginx die Anfrage nur an uWSGI weiterleitet, glaube ich, dass es möglicherweise etwas gibt, was die Ursache dafür ist.

Hier ist meine ini-Konfiguration für die Domain für uWSGI, die ich im Kaisermodus betreibe:

[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
10
smaili

Dieses Zeug ist der eigentliche und wahrscheinlich wichtigste Anwendungsfall für Python Celery ( http://www.celeryproject.org/ ). Führen Sie in der Regel keine langen Jobs aus, die im Prozess wsgi CPU-gebunden sind. Das ist kompliziert, ineffizient und vor allem komplizierter als das Einrichten einer async -Aufgabe in einem Selleriearbeiter. Wenn Sie nur einen Prototyp erstellen möchten, können Sie den Broker auf memory setzen und keinen externen Server verwenden oder einen einzelnen Thread redis auf demselben Computer ausführen. 

Auf diese Weise können Sie die Task starten, nämlich task.result() aufrufen, die blockiert, aber blockiert auf IO-gebundene Weise , oder, noch besser, Sie können sofort zurückkehren, indem Sie den task_id abrufen und einen zweiten Endpunkt erstellen /result?task_id=<task_id> überprüft, ob das Ergebnis verfügbar ist:

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

Auf diese Weise verfügen Sie über eine nicht blockierende wsgi-App, die das tut, was am besten geeignet ist: kurze Aufrufe von CPU-ungebundenen Verbindungen mit IO -Aufrufen, die höchstens mit OS-Zeitplanaufrufen ausgeführt werden können, dann können Sie sich direkt auf die wsgi verlassen. Server workers|processes|threads oder was auch immer Sie benötigen, um die API in jedem beliebigen Wsgi-Server wie uwsgi, gunicorn usw. für die 99% der Workloads als Sellerie-Skalierung horizontal zu skalieren, indem Sie die Anzahl der Arbeitsprozesse erhöhen.

3
danius

Dieser Ansatz funktioniert für mich, er ruft den Timeout-Befehl (sleep 10s) in der Befehlszeile auf und lässt ihn im Hintergrund arbeiten. Die Antwort wird sofort zurückgegeben. 

@app.route('/endpoint1')
def endpoint1():
    subprocess.Popen('timeout 10', Shell=True)
    return 'success1'

Allerdings nicht auf WSGI-Server testen, sondern nur lokal.

0
Joost