Создание папки для создания расы в Python

У меня есть модуль кэширования urllib2, который спорадически выходит из строя из-за следующего кода:

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

Проблема заключается в том, что к моменту выполнения второй строки папка может существовать и будет иметь ошибку:

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

Это связано с тем, что script одновременно запускается много раз, сторонним кодом у меня нет контроля.

Код (до того, как я попытался исправить ошибку) можно найти здесь, на github

Я не могу использовать tempfile.mkstemp, поскольку он решает условие гонки, используя случайно названный каталог (tempfile.py источник здесь), что приведет к поражению цели кеша.

Я не хочу просто отбрасывать ошибку, так как ошибка при ошибке Errno 17 возникает, если имя папки существует как файл (другая ошибка), например:

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

Я не могу использовать threading.RLock, поскольку код вызывается из нескольких процессов.

Итак, я попробовал написать простую блокировку на основе файлов (эту версию можно найти здесь), но это имеет проблему: она создает файл блокировки на один уровень вверх, поэтому /tmp/example.lock для /tmp/example/, который прерывается, если вы используете /tmp/ в качестве кеша dir (поскольку он пытается сделать /tmp.lock)..

Короче, мне нужно кэшировать ответы urllib2 на диск. Для этого мне нужно получить доступ к известному каталогу (создав его, если требуется), безопасным способом многопроцессорности. Он должен работать на OS X, Linux и Windows.

Мысли? Единственное альтернативное решение, о котором я могу думать, - это переписать модуль кэша с использованием хранилища SQLite3, а не файлов.

Ответ 1

В Python 3.x вы можете использовать os.makedirs(path, exists_ok=True), который не вызовет никаких исключений, если такой каталог существует. Он вызовет FileExistsError: [Errno 17], если существует файл с тем же именем, что и запрошенный каталог (path).

Проверьте это с помощью:

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)

Ответ 2

Вместо

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

вы могли бы сделать

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

По мере того как вы получите ту же функциональность.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я не знаю, как это могло быть Pythonic.


Использование SQLite3, может быть немного перебор, но добавит много функциональности и гибкости в ваш прецедент.

Если вам нужно сделать много "выбор", одновременную вставку и фильтрацию, отличная идея использовать SQLite3, поскольку она не добавит слишком много сложностей по сравнению с простыми файлами (можно утверждать, что она устраняет сложность).


Перечитывая свой вопрос (и комментарии), я могу лучше понять вашу проблему.

Какова вероятность того, что файл может создать одно и то же состояние гонки?

Если он достаточно мал, я бы сделал что-то вроде:

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

Кроме того, прочитав свой код, я бы изменил

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

поскольку это действительно то, что вы хотите, Python для ререйза точно такого же исключения (просто nitpicking).


Еще одна вещь, может быть

Ответ 3

Код, с которым я закончил, был:

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 a directory,
        # another process beat us to creating this dir, that OK.
        pass
    else:
        # Our target dir exists as a file, or different error,
        # reraise the error!
        raise

Ответ 4

Вы можете поймать исключение, а затем проверить, существует ли файл в качестве каталога или нет?

Ответ 5

Когда у вас есть условия гонки часто EAFP (проще просить прощения, чем разрешение) работает лучше, чем LBYL (смотрите, прежде чем прыгать)

Стратегии проверки ошибок