Question

J'ai un module de mise en cache de urllib2, qui tombe en panne de façon sporadique en raison du code suivant:

if not os.path.exists(self.cache_location):
    os.mkdir(self.cache_location)

Le problème est, au moment où la deuxième ligne est en cours d'exécution, le dossier peut exister, et l'erreur:

  File ".../cache.py", line 103, in __init__
    os.mkdir(self.cache_location)
OSError: [Errno 17] File exists: '/tmp/examplecachedir/'

En effet, le script est lancé en même temps à plusieurs reprises, par code tiers que je ne contrôle pas.

Le code (avant que je tenté de corriger le bug) peut être trouvé ici, sur GitHub

Je ne peux pas utiliser le tempfile.mkstemp , comme permet de résoudre la condition de course en utilisant un répertoire nommé de façon aléatoire ( source de tempfile.py ici ), ce qui irait à l'encontre du cache.

Je ne veux pas simplement jeter l'erreur, comme la même erreur erreur Errno 17 est augmentée si le nom du dossier existe sous la forme d'un fichier (une autre erreur), par exemple:

$ touch blah
$ python
>>> import os
>>> os.mkdir("blah")
Traceback (most recent call last):
  File "", line 1, in 
OSError: [Errno 17] File exists: 'blah'
>>>

Je ne peux pas en utilisant threading.RLock que le code est appelé à partir de plusieurs processus.

Alors, j'ai essayé d'écrire simple verrouillage basé sur des fichiers ( cette version peut être trouvé ici ), mais cela a un problème: il crée le lockfile un niveau, donc /tmp/example.lock pour /tmp/example/, qui brise si vous utilisez /tmp/ comme un cache dir (comme il essaie de faire /tmp.lock) ..

En bref, je dois mettre en cache des réponses urllib2 à disque. Pour ce faire, je dois accéder à un répertoire connu (création, le cas échéant), dans un multiprocessus de manière sûre. Il a besoin de travailler sur OS X, Linux et Windows.

Pensées? La seule solution de rechange que je peux penser est de réécrire le module de cache à l'aide de stockage SQLite3, plutôt que des fichiers.

Était-ce utile?

La solution

En Python 3.x, vous pouvez utiliser os.makedirs(path, exists_ok=True), qui ne soulèvera pas d'exception si ce répertoire existe. Il soulèvera FileExistsError: [Errno 17] si un fichier existe avec le même nom que le répertoire demandé (path).

Vérifier avec:

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)

Autres conseils

Au lieu de

if not os.path.exists(self.cache_location):
    os.mkdir(self.cache_location)

vous pouvez faire

try:
    os.makedirs(self.cache_location)
except OSError:
    pass

Comme vous finiriez avec le même fonctionnalité .

DISCLAIMER: Je ne sais pas comment cela pourrait être Pythonic

.

Utilisation SQLite3, peut être un peu exagéré, mais ajouterait un beaucoup de la fonctionnalité et la flexibilité de votre cas d'utilisation.

Si vous devez faire beaucoup de « sélection », l'insertion simultanée et le filtrage, il est une excellente idée d'utiliser SQLite3, car il ajoute habitude trop de complexité sur les fichiers simples (on pourrait dire qu'il supprime la complexité).


Relire votre question (et commentaires) je peux mieux comprendre votre problème.

Quelle est la possibilité qu'un fichier pourrait créer la même condition de course?

S'il est assez petit, alors je ferais quelque chose comme:

if not os.path.isfile(self.cache_location):
    try:
        os.makedirs(self.cache_location)
    except OSError:
        pass

En outre, la lecture de votre code, je changerais

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

comme il est vraiment ce que vous voulez, Python reraise la même exception exacte (juste tatillonne) .


Une autre chose, peut-être script shell cette pourrait être d'utilisation pour vous (Unix uniquement).

Le code j'ai fini avec était:

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

Pouvez-vous attraper l'exception, puis vérifier si le fichier existe comme un répertoire ou non?

Lorsque vous avez des conditions de course souvent EAFP (plus facile de demander pardon que la permission) fonctionne mieux que LBYL (regarder avant de sauter)

stratégies de vérification d'erreurs

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top