Race-Bedingung zu schaffen Ordner in Python
-
22-09-2019 - |
Frage
Ich habe ein urllib2 Caching-Modul, das sporadisch aufgrund des folgenden Code abstürzt:
if not os.path.exists(self.cache_location):
os.mkdir(self.cache_location)
Das Problem wird durch die Zeit, die zweite Zeile ausgeführt wird, kann der Ordner vorhanden ist, und wird Fehler:
File ".../cache.py", line 103, in __init__ os.mkdir(self.cache_location) OSError: [Errno 17] File exists: '/tmp/examplecachedir/'
Das ist, weil das Skript gleichzeitig mehrfach gestartet wird, die von Drittanbieter-Code ich keine Kontrolle habe.
Der Code (bevor ich versuchte, den Fehler zu beheben) kann hier zu finden, auf Github
kann ich nicht die tempfile.mkstemp , wie es löst die race-Bedingung durch ein zufällig benannte Verzeichnis mit ( tempfile.py Quelle hier ), die den Zweck der Cache besiegen würde.
Ich will nicht einfach, die Fehler verwerfen, da die gleichen Fehler Errno 17 Fehler ausgelöst werden, wenn der Ordnername als Datei vorhanden ist (ein anderer Fehler), zum Beispiel:
$ touch blah $ python >>> import os >>> os.mkdir("blah") Traceback (most recent call last): File "", line 1, in OSError: [Errno 17] File exists: 'blah' >>>
Ich kann nicht mit threading.RLock
als der Code von mehreren Prozessen aufgerufen wird.
So habe ich versucht, eine einfache dateibasierte Sperre Schreiben ( diese Version kann sein hier ), aber dies hat ein Problem: es schafft die lockfile eine Ebene nach oben, so /tmp/example.lock
für /tmp/example/
, die Pausen, wenn Sie /tmp/
als Cache-Verzeichnis verwenden (wie es versucht /tmp.lock
) ..
Kurz gesagt, muss ich Cache urllib2
Antworten auf der Disc. Um dies zu tun, muß ich ein bekanntes Verzeichnis zugreifen (Erstellen es, falls erforderlich), in einem Multi-Prozess sicher. Es muss Arbeit auf OS X, Linux und Windows.
Die Gedanken? Die einzige Alternative Lösung, die ich denken kann, ist das Cache-Modul mit SQLite3 Speichern neu zu schreiben, anstatt Dateien.
Lösung
In Python 3.x können Sie verwenden os.makedirs(path, exists_ok=True)
, die jede Ausnahme nicht erhöhen wird, wenn solches Verzeichnis existiert. Es wird erhöhen FileExistsError: [Errno 17]
, wenn eine Datei mit dem gleichen Namen wie das angeforderte Verzeichnis (path
) vorhanden ist.
Stellen Sie sicher, es mit:
import os
parent = os.path.dirname(__file__)
target = os.path.join(parent, 'target')
os.makedirs(target, exist_ok=True)
os.makedirs(target, exist_ok=True)
os.rmdir(target)
with open(target, 'w'):
pass
os.makedirs(target, exist_ok=True)
Andere Tipps
Anstelle von
if not os.path.exists(self.cache_location):
os.mkdir(self.cache_location)
Sie tun können,
try:
os.makedirs(self.cache_location)
except OSError:
pass
Wie Sie mit dem gleichen enden würden Funktionalität .
HAFTUNGSAUSSCHLUSS:. Ich weiß nicht, wie Pythonic dies sein könnte
Mit SQLite3
, könnte ein bisschen zu viel des Guten, aber füge hinzu, ein Los von Funktionalität und Flexibilität, um Ihren Anwendungsfall.
Wenn Sie eine Menge von „Auswahl“ zu tun haben, gleichzeitige Einsetzen und Filterung, es ist eine gute Idee zu verwenden SQLite3
, da es zu viel Komplexität über einfache Dateien hinzufügen werde nicht (es könnte argumentiert werden, dass es die Komplexität entfernt).
Rereading Ihre Frage (und Kommentare) ich Ihr Problem besser verstehen kann.
Was ist die Möglichkeit, dass eine Datei könnte die gleiche Race-Bedingung erstellen?
Wenn es klein genug ist, dann würde ich so etwas wie:
if not os.path.isfile(self.cache_location):
try:
os.makedirs(self.cache_location)
except OSError:
pass
Auch der Code liest, würde ich ändern
else:
# Our target dir is already a file, or different error,
# relay the error!
raise OSError(e)
else:
# Our target dir is already a file, or different error,
# relay the error!
raise
wie es ist wirklich das, was Sie wollen, Python genau die gleiche Ausnahme Reraise (nur pingelig) .
Eine weitere Sache, können diese sein könnte Bedienung für Sie (Unix-only).
Der Code, den ich am Ende mit war:
import os
import errno
folder_location = "/tmp/example_dir"
try:
os.mkdir(folder_location)
except OSError as e:
if e.errno == errno.EEXIST and os.path.isdir(folder_location):
# File exists, and it's a directory,
# another process beat us to creating this dir, that's OK.
pass
else:
# Our target dir exists as a file, or different error,
# reraise the error!
raise
Könnten Sie die Ausnahme abfangen und dann prüfen, ob die Datei vorhanden ist als Verzeichnis oder nicht?
Wenn Sie Rennbedingungen haben oft EAFP (einfacher zu fragen, Vergebung als Erlaubnis) funktioniert besser, dass LBYL (Blick, bevor Sie springen)