2009-10-19 9 views
13

Mam moduł buforowania urllib2, co sporadycznie rozbija się z następującego kodu:Race stan tworzenia folder Pythonie

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

Problem, zanim druga linia jest wykonana, może folder istnieje i będzie błąd:

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

Dzieje się tak, ponieważ skrypt jest jednocześnie uruchamiany wiele razy, przy użyciu kodu innej firmy, nad którym nie mam kontroli.

Kod (przed Starałem się usunąć błędu) można znaleźć here, on github

nie mogę użyć tempfile.mkstemp, ponieważ rozwiązuje sytuacji wyścigu przy użyciu losowo nazwie katalogu (tempfile.py source here), który pokonał cel pamięci podręcznej.

Nie chcę po prostu usunąć ten błąd, ponieważ tego samego błędu errno 17 Błąd jest podniesione, jeśli nazwa folderu istnieje jako plik (inny błąd), na przykład:

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

nie mogę przy użyciu threading.RLock, ponieważ kod jest wywoływany z wielu procesów.

Tak, próbowałem pisać prostą blokadę na podstawie pliku (that version can be found here) A, ale to nie ma wątpliwości: to tworzy plik blokujący jeden poziom w górę, więc /tmp/example.lock dla /tmp/example/, który łamie jeśli używasz /tmp/ jako cache dir (jak próbuje uzyskać /tmp.lock) ..

Krótko mówiąc, muszę buforować odpowiedzi urllib2 na dysku. Aby to zrobić, muszę uzyskać dostęp do znanego katalogu (jeśli jest to wymagane), w sposób bezpieczny dla wielu procesorów. Musi działać na OS X, Linux i Windows.

Myśli? Jedynym alternatywnym rozwiązaniem, które mogę wymyślić, jest przepisanie modułu pamięci podręcznej przy użyciu pamięci SQLite3, a nie plików.

Odpowiedz

5

Kod skończyło się było:

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

Czy możesz złapać wyjątek, a następnie sprawdzić, czy plik istnieje jako katalog, czy nie?

+0

Prawdopodobnie! Zastanowiłem się nad tym tak samo, jak przed ponownym przeczytaniem pytania, zanim je przesłałem. Zaimplementowałem to (http://github.com/dbr/tvdb_api/blob/468d9f816373b14ef3a483fca07e031b69fa62f9/cache.py#L103-114), i sprawi, że osoba, która zgłosiła błąd, przetestuje go wkrótce. – dbr

+0

To wydaje się działać idealnie, dzięki! – dbr

+1

@dbr: zauważ, że w linii 114, chcesz 'raise e', ponieważ jest to już instancja' OSError'. http://github.com/dbr/tvdb_api/blob/468d9f816373b14ef3a483fca07e031b69fa62f9/cache.py#L114 – nosklo

11

Zamiast

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

można zrobić

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

Jak można skończyć z tym samym funkcjonalności.

OŚWIADCZENIE: Nie wiem, w jaki sposób może to być Python.


Korzystanie SQLite3, może być trochę przesada, ale byłoby dodać dużo funkcjonalności i elastyczności w przypadku użycia.

Jeśli musisz dużo "wybierać", współbieżne wstawianie i filtrowanie, dobrym pomysłem jest użycie SQLite3, ponieważ nie spowoduje to nadmiernej złożoności w stosunku do prostych plików (można argumentować, że usuwa złożoność).


Ponowne wyświetlenie Twojego pytania (i komentarzy) Mogę lepiej zrozumieć Twój problem.

Jaka jest możliwość, że plik może utworzyć ten sam stan wyścigu?

Jeśli jest wystarczająco małe, to zrobiłbym coś takiego:

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

Również czytanie kodu, chciałbym zmienić

else: 
    # Our target dir is already a file, or different error, 
    # relay the error! 
    raise OSError(e) 

do

else: 
    # Our target dir is already a file, or different error, 
    # relay the error! 
    raise 

ponieważ tak naprawdę to jest to, czego chcesz, Python przeszukuje dokładnie ten sam wyjątek: (tylko nitpicking).


Jeszcze jedno, może być this mógłby być użyteczny dla Ciebie (tylko Unix-like).

+2

+1: Jest idealnie Pythonic, IMO. –

+1

Uzgodniono, choć można rozważyć użycie 'os.makedirs' zamiast tego, które również tworzą katalogi nadrzędne, jeśli to konieczne. –

+0

@Eli Courtwright: dobry punkt. Zmodyfikowany. – voyager

2

Kiedy masz warunków wyścigu często EAFP (łatwiej jest prosić o przebaczenie niż pozwolenie) działa lepiej, że LBYL (patrzeć przed skokiem)

Error checking strategies

+0

Wszystko zależy od prawdopodobieństwa konfliktu - EAFP = optymistyczna współbieżność, która sprawdza konflikt po wypróbowaniu operacji i działa dobrze, jeśli istnieje małe prawdopodobieństwo konfliktów. LBYL = pesymistyczna współbieżność, która sprawdza przed operacją i jest lepsza, jeśli zazwyczaj występuje wiele konfliktów. W przypadku takiego prostego przypadku myślę, że pesymizm jest lepszy. – RichVel

+0

@RichVel, z LBYL tutaj masz możliwość wyścigu. Potrzebujesz więc dodatkowego kodu do obsługi wyjątku. Jest to tylko prosta operacja, a dodatkowy kod to niewiele, ale te rzeczy mają coraz większy wpływ na całe życie projektu. Za każdym razem, gdy ktoś potrzebuje przeczytać/zrozumieć/debugować ten dodatkowy kod, jest dodatkowy koszt. Myślę, że to prawdopodobnie przedwczesna i niepotrzebna optymalizacja w tym przypadku. –

+0

Tak nie jest, ponieważ mkdir jest atomowy, tj. Jest równoważny z pojedynczą operacją "testuj i ustaw". Kodowanie operacji LBYL z testem nieatomowym i ustawionymi operacjami spowoduje warunki wyścigu, ale jest to błąd implementacji. – RichVel