2009-10-19 1 views
13

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

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/'

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

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

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

Я не хочу, чтобы просто сбросить ошибку, как та же ошибка возникает 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, поскольку код вызывается из нескольких процессов.

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

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

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

ответ

5

код я закончил с был:

import os 
import errno 

folder_location = "/tmp/example_dir" 

try: 
    os.mkdir(folder_location) 
except OSError, 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 
2

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

+0

Возможно! Я подумал об этом, так же, как я перечитывал вопрос, прежде чем отправлять его. Я реализовал это (http://github.com/dbr/tvdb_api/blob/468d9f816373b14ef3a483fca07e031b69fa62f9/cache.py#L103-114), и получит человека, который сообщил об ошибке, чтобы проверить его в ближайшее время. – dbr

+0

Это, кажется, работает отлично, спасибо! – dbr

+1

@dbr: обратите внимание, что в строке 114 вы хотите 'raise e', так как это уже экземпляр' OSError'. http://github.com/dbr/tvdb_api/blob/468d9f816373b14ef3a483fca07e031b69fa62f9/cache.py#L114 – nosklo

11

Вместо

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). более


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

+2

+1: Это отлично Pythonic, IMO. –

+1

Согласны, хотя вы можете вместо этого использовать 'os.makedirs', которые также при необходимости создают родительские каталоги. –

+0

@Eli Courtwright: хорошая точка. Modified. – voyager

2

Если у вас есть условия гонки часто ЭСПЦ (проще попросить прощения, чем разрешения) работает лучше, LBYL (раз отмерь)

Error checking strategies

+0

Все зависит от вероятности конфликтов. EAFP = оптимистичный параллелизм, который проверяет конфликт после попытки операции и работает хорошо, если есть небольшая вероятность конфликтов. LBYL = пессимистический параллелизм, который проверяет перед операцией и лучше, если есть много конфликтов. Для простого случая, подобного этому, я думаю, что пессимистично лучше. – RichVel

+0

@ RichVel, с LBYL здесь у вас есть возможность гонки. Поэтому вам нужен дополнительный код для обработки исключения. Таким образом, это всего лишь простая операция, и лишний код сейчас не так много, но у этих вещей есть способ расти в течение всего жизненного цикла проекта. Существует дополнительная плата каждый раз, когда кто-то должен читать/понимать/отлаживать этот дополнительный код. Я думаю, что это скорее всего будет преждевременная и ненужная оптимизация в этом случае. –

+0

Это не так, потому что mkdir является атомарным, то есть эквивалентно одной «тестовой и заданной» операции. Кодирование операций LBYL с неатомными испытаниями и установками приведет к условиям гонки, но это ошибка реализации. – RichVel

Смежные вопросы