1

(Complete Test-App auf GitHub: https://github.com/olingerc/socketio-copy-large-file)Flask-socketio vermisst Ereignisse während Thread-Datei im Hintergrund zu kopieren

I Flask bin mit zusammen mit der Kolben-SocketIO Plugin. Meine Clients können den Server bitten, Dateien über Websocket zu kopieren, aber während die Dateien kopiert werden, möchte ich, dass die Clients mit dem Server kommunizieren können, um ihn um andere Dinge zu bitten. Meine Lösung besteht darin, den Kopiervorgang (shuthil) in einem Hintergrundthread auszuführen. Dies ist die Funktion:

def copy_large_file(): 
    source = "/home/christophe/Desktop/largefile" 
    destination = "/home/christophe/Desktop/largefile2" 
    try: 
     os.remove(destination) 
    except: 
     pass 
    print("Before copy") 
    socketio.emit('my_response', 
        {'data': 'Thread says: before'}, namespace='/test') 
    shutil.copy(source, destination) 
    print("After copy") 
    socketio.emit('my_response', 
        {'data': 'Thread says: after'}, namespace='/test') 

ich folgendes Verhalten beobachten: Wenn die Funktion Starten des nativen socketio Methode:

socketio.start_background_task(target=copy_large_file) 

alle eingehenden Ereignisse während eine große Datei, bis die verzögert wird kopiert Die Datei ist fertig und eine nächste Datei wird gestartet. Ich gues shutil nicht ist relasing die GIL oder so ähnlich, so dass ich getestet mit einem Gewinde:

thread = threading.Thread(target=copy_large_file) 
thread.start() 

gleiches Verhalten. Vielleicht Multiprozessing?

thread = multiprocessing.Process(target=copy_large_file) 
thread.start() 

Ah! Das funktioniert und Signale, die über socketio innerhalb der Funktion copy_large_file ausgegeben werden, werden korrekt empfangen. ABER: Wenn ein Benutzer beginnt, eine sehr große Datei zu kopieren, schließt ihren Browser und kommt 2 Minuten später zurück, verbindet sich der Socket nicht mehr mit der gleichen Socket "Sitzung"? und empfängt somit keine Nachrichten mehr, die vom Hintergrundprozess ausgegeben wurden.

Ich denke, die Hauptfrage ist: Wie kann ich große Dateien im Hintergrund kopieren, ohne flask-socket zu blockieren, aber immer noch in der Lage sein, Signale aus dem Hintergrundprozess an den Client zu senden.

im Browser:

  • zu localhost gehen: 5000
  • Klicken Sie auf Copy
  • Klicken Sie auf Ping-Datei eine Nachricht zu senden, während die Datei
  • kopiert wird
  • beobachten auch für andere Signale von Hintergrund-Thread
+0

Was ist mit der Zuweisung einer Zimmer-ID an den Client, dann senden Sie eine Nachricht an den Raum statt. Wenn der Client zurückkommt, treten Sie dem vorherigen Raum bei. –

Antwort

1

Sie stellen zwei getrennte Fragen.

Zuerst besprechen wir das tatsächliche Kopieren der Datei.

Es scheint, als ob Sie Eventlet für Ihren Server verwenden. Während dieses Framework asynchrone Ersetzungen für Netzwerk-E/A-Funktionen bietet, ist die Festplatten-E/A auf nicht-blockierende Weise viel komplizierter, insbesondere unter Linux (einige Informationen zum Problem here).So verursacht I/O auf Dateien sogar mit der Standard-Bibliothek Affe Patched blockiert, wie Sie bemerkt haben. Das ist übrigens bei gevent genauso.

Eine typische Lösung zum Ausführen nicht blockierender E/A in Dateien ist die Verwendung eines Thread-Pools. Mit Eventlet kann die eventlet.tpool.execute Funktion dies tun. Also im Grunde, anstatt copy_large_file() direkt anrufen, rufen Sie tpool.execute(copy_large_file). Dadurch können andere grüne Threads in Ihrer Anwendung ausgeführt werden, während die Kopie in einem anderen Systemthread stattfindet. Ihre Lösung, einen anderen Prozess zu verwenden, ist übrigens auch gültig, aber es kann zu viel werden, je nachdem, wie oft und wie oft Sie eine dieser Kopien erstellen müssen.

Ihre zweite Frage bezieht sich auf das "Erinnern" eines Clients, der eine lange Dateikopie startet, auch wenn der Browser geschlossen und erneut geöffnet wird.

Dies ist etwas, das Ihre Anwendung verarbeiten muss, indem sie den Status speichert, der erforderlich ist, um einen zurückkehrenden Client wiederherzustellen. Vermutlich haben Ihre Kunden eine Möglichkeit, sich entweder mit einem Token oder einer anderen Identifikation mit Ihrer Anwendung zu identifizieren. Wenn der Server eine dieser Dateikopien startet, kann er der Operation eine ID zuweisen und diese ID in einer Datenbank speichern, die dem Client zugeordnet ist, der sie angefordert hat. Wenn der Client weggeht und dann zurückkehrt, können Sie feststellen, ob noch laufende Dateikopien dafür vorhanden sind, und auf diese Weise den Client wieder so synchronisieren, wie er vor dem Schließen des Browsers war.

Hoffe, das hilft!

+0

Große Antwort. Hinweis: Sie können nur die tatsächlichen blockierenden Aufrufe in tpool einfügen. 'eventlet.tpool.execute (os.remove, Pfad)' und dasselbe für 'shuthil.copy'. – temoto

+0

@temoto ja, das ist auch eine Option. – Miguel

+0

@Miguel. Hmm, ich habe versucht, die tpool-Lösung, aber ich bekomme die gleichen Ergebnisse. Die gesamte Anwendung ist blockiert. Die Dokumentation besagt sogar: Die Funktion wird in einem zufälligen Thread im Pool ausgeführt, während die aufrufende Coroutine nach ihrem Abschluss blockiert. – christophe

Verwandte Themen