Папка для создания условий гонки в Python

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

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

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

 Файл ".../cache.py", строка 103, в __init__
    os.mkdir(self.cache_location)
Ошибка OSE: [Errno 17] Файл существует: '/tmp/examplecachedir/' 

Это потому, что сценарий запускается одновременно несколько раз, с помощью стороннего кода, который я не могу контролировать.

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

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

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

 Touch Touch бла
$ python
>>> импорт ОС
>>> os.mkdir("бла")
Traceback (последний вызов был последним):
  Файл "", строка 1, в 
OSError: [Errno 17] Файл существует: 'бла'
>>> 

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

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

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

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

4 ответа

Решение

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

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

Вместо

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 возродит точно такое же исключение (просто придирки).


Еще одна вещь, может быть, это может быть полезно для вас (только для Unix).

В Python 3.x вы можете использовать os.makedirs(path, exist_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)

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

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

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

Другие вопросы по тегам