Python file I/O auf Windows und Linux sind zwei verschiedene Dinge
Bei der Verwendung von Threads kann es zu Problemen kommen, die durch das unterschiedliche Verhalten von Datei-E/A-Funktionen verursacht werden.
Ich habe ein Python -Programm, das problemlos auf Linux läuft. Vor ein paar Monaten wollte ich es auf Windows laufen lassen.
Dies war das erste Mal, dass ich Python auf Windows verwendet habe. Python App installieren, virtual environment erstellen, kopieren und ausführen. Keine Probleme ... oh, aber es gab ein Problem. Meine Sitzung verschwand manchmal ... WTF! Ich bemerkte das Problem durch wiederholtes Drücken von F5 in sehr kurzer Zeit. Zeit für eine gründliche Untersuchung.
Die Anwendung und der Sitzungs-Schlüsselwertspeicher
Die Anwendung Python ist eine Flask -Anwendung. Sie verwendet die Dateisystemschnittstelle von Flask-Session, um Sessions als Dateien zu speichern. Flask-Session verwendet ein anderes PyPi-Paket, um alle Datei-E/A zu behandeln. Wie Sie sich vorstellen können, implementiert dieses Paket einen Key-Value-Speicher.
Es gibt zwei Hauptmethoden in diesem Paket:
- set(key, value), zum Schreiben/Aktualisieren der Sitzungsdaten
- get(key), zum Abrufen der Sitzungsdaten
Im Paket erstellt die Methode set(key, value) eine temporäre Datei und ruft dann die Funktion Python os.replace() auf, um die Session-Datei zu ersetzen. Die Methode get(key) ruft die Lesefunktion Python auf.
Test mit Threads
Wenn Sie Flask im Entwicklungsmodus ausführen, werden Sie viele Anfragen an den Sitzungsspeicher sehen, da Flask auch images, CSS-Dateien usw. bereitstellt. In einer Produktionsumgebung, in der Sie statische Inhalte über einen Webserver bereitstellen, ist dieses Problem zwar weniger wahrscheinlich, aber es besteht trotzdem!
So kam ich auf die Idee, einen kleinen Test mit Threads zu schreiben.
import cachelib
import threading
fsc = cachelib.file.FileSystemCache('.')
def set_get(i):
fsc.set('key', 'val')
val = fsc.get('key')
for i in range(10):
t = threading.Thread(target=set_get, args=(i,))
t.start()
Auf Linux gab es keine Fehler, nichts. Aber auf Windows führte dies zu zufällig generierten Ausnahmen:
[WinError 5] Access is denied
...
[Errno 13] Permission denied
Die Ausnahme [WinError 5] wurde von der Funktion Python os.replace() erzeugt. Die [Errno 13]-Ausnahme wurde von der Funktion Python read() erzeugt.
Was ist hier los?
File I/O auf Windows und Linux sind zwei verschiedene Dinge
Ich nahm an, dass Python mich vor plattformspezifischen Implementierungen schützen würde. Das ist bei vielen Funktionen der Fall, aber nicht bei allen. Insbesondere bei der Verwendung von Threads kann es zu Problemen kommen, die durch unterschiedliches Verhalten von Datei-E/A-Funktionen verursacht werden.
Aus Python Bug Tracker Issue46003:
Wie man so schön sagt, gibt es keine 'portable Software', sondern nur 'Software, die portiert wurde'. Besonders in einem Bereich wie Datei-E/A: Sobald man über das einfache 'ein Prozess öffnet, schreibt und schließt' und ein anderer Prozess dann 'öffnet, liest und schließt' hinausgeht, gibt es eine Menge plattformspezifischer Probleme. Python versucht nicht, alle möglichen Datei-I/O-Probleme weg zu abstrahieren. |
Python read()-Funktion
In der Bibliothek, die ich verwende, verwendet die Methode get(key) die Funktion Python read().
Bei Linux genügt es, die read()-Funktion in ein try-except zu setzen:
try:
with open(f, 'r') as fo:
return fo.read()
except Exception as e:
return None
Die Funktion wartet dann, bis die Daten verfügbar sind. Eine Ausnahme wird nur dann ausgelöst, wenn ein Timeout auftritt, der auf den meisten Linux -Systemen 60 Sekunden beträgt, oder ein anderer unerwarteter Fehler auftritt.
Auf Windows wird dies sofort fehlschlagen, wenn ein anderer Thread auf die Datei zugreift. Um das gleiche Verhalten wie bei Linux zu erreichen, müssen wir Wiederholungen und eine Verzögerung hinzufügen:
max_sleep_time = 10
total_sleep_time = 0
sleep_time = 0.02
while total_sleep_time < max_sleep_time:
try:
with open(f, 'r') as fo:
return fo.read()
except OSError as e:
errno = getattr(e, 'errno', None)
if errno == 13:
# permission error
time.sleep(sleep_time)
total_sleep_time += sleep_time
sleep_time *= 2
else:
# some other error
return None
except Exception as e:
return None
# out of retries
return None
Python os.replace() Funktion
In der Bibliothek, die ich verwende, verwendet die Methode set(key, value) die Funktion Python os.replace().
Bei Linux reicht es aus, die os.replace()-Funktion in ein try-except zu setzen:
try:
os.replace(src, dst)
return True
except Exception as e:
return False
Die Funktion wird warten, bis die Datei ersetzt werden kann. Eine Ausnahme wird nur dann ausgelöst, wenn eine Zeitüberschreitung auftritt, die auf den meisten Linux -Systemen 60 Sekunden beträgt, oder ein anderer unerwarteter Fehler auftritt.
Auf Windows wird dies sofort fehlschlagen, wenn ein anderer Thread auf die Datei zugreift. Um das gleiche Verhalten wie bei Linux zu erreichen, müssen wir z. B. Wiederholungen und eine Verzögerung hinzufügen:
max_sleep_time = 10
total_sleep_time = 0
sleep_time = 0.02
while total_sleep_time < max_sleep_time:
try:
os.replace(src, dst)
return True
except Exception as e:
winerror = getattr(e, 'winerror', None)
if winerror == 5:
time.sleep(sleep_time)
total_sleep_time += sleep_time
sleep_time *= 2
else:
# some other error
return False
# out of retries
return False
Schlussfolgerung
Die Erstellung eines Python -Programms, das auf mehreren Plattformen laufen kann, kann kompliziert sein, da man auf Probleme wie die oben beschriebenen stoßen kann. Zuerst war ich überrascht, dass Python die Komplexität von Windows nicht vor mir versteckt hat. Da ich von Linux komme, dachte ich: Python , warum funktioniert das nicht auf Windows so, wie es auf Linux funktioniert?
Aber das ist die Entscheidung, die die Python -Entwickler getroffen haben. Vielleicht ist es auch nicht möglich. Ich konnte online keine einzige Zeile in den Python -Dokumenten finden, die mich darauf aufmerksam gemacht hätte, und habe festgestellt, dass viele Leute mit diesem Problem zu kämpfen haben. Ich reichte einen Fehlerbericht ein und bat darum, dass eine Warnung für Entwickler hinzugefügt wird, wenn sie für mehrere Plattformen entwickeln. Später habe ich davon Abstand genommen, weil ich weiß, dass ich sehr voreingenommen bin, wenn ich von Linux komme.
Links / Impressum
backports.py
https://github.com/flennerhag/mlens/blob/master/mlens/externals/joblib/backports.py
os.replace
https://docs.python.org/3/library/os.html?highlight=os%20replace#os.replace
os.replace is not cross-platform: at least improve documentation
https://bugs.python.org/issue46003
Mehr erfahren
Threads
Neueste
- Ausblenden der Primärschlüssel der Datenbank UUID Ihrer Webanwendung
- Don't Repeat Yourself (DRY) mit Jinja2
- SQLAlchemy, PostgreSQL, maximale Anzahl von Zeilen pro user
- Anzeige der Werte in den dynamischen Filtern SQLAlchemy
- Sichere Datenübertragung mit Public Key Verschlüsselung und pyNaCl
- rqlite: eine hochverfügbare und distverteilte SQLite -Alternative
Meistgesehen
- Verwendung von Pythons pyOpenSSL zur Überprüfung von SSL-Zertifikaten, die von einem Host heruntergeladen wurden
- Verwendung von UUIDs anstelle von Integer Autoincrement Primary Keys mit SQLAlchemy und MariaDb
- PyInstaller und Cython verwenden, um eine ausführbare Python-Datei zu erstellen
- Verbindung zu einem Dienst auf einem Docker -Host von einem Docker -Container aus
- SQLAlchemy: Verwendung von Cascade Deletes zum Löschen verwandter Objekte
- Flask RESTful API Validierung von Anfrageparametern mit Marshmallow-Schemas