2009-10-19 64 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/'

這是因爲腳本是同時啓動無數次,我無法控制的第三方代碼。

的代碼(之前,我試圖修復bug),可以發現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來重新評估完全相同的異常(只是挑剔)


一兩件事,可能是this可能對你有用的(類似Unix只)。

+2

+1:這是完全符合Python,IMO。 –

+1

同意,儘管你可能會考慮使用'os.makedirs'來代替,如果需要的話,它也會創建父目錄。 –

+0

@Eli Courtwright:好點。改性。 – voyager

2

當你有競爭條件經常EAFP(更容易請求原諒比許可)的作品更好的是LBYL(三思而後行)

Error checking strategies

+0

這一切都取決於衝突的可能性 - EAFP =樂觀併發性,它在嘗試操作後檢查衝突,如果衝突的可能性很小,則工作良好。 LBYL =悲觀併發性,在操作之前進行檢查,如果通常存在許多衝突,則更好。對於這樣的簡單情況,我認爲悲觀會更好。 – RichVel

+0

@RichVel,與LBYL在這裏你有一個競爭條件的可能性。所以你需要額外的代碼來處理異常。所以這只是一個簡單的操作,現在額外的代碼並不多,但這些東西在項目的整個生命週期中都有增長的方式。每次有人需要閱讀/理解/調試這些額外的代碼時,都會產生額外的成本。我認爲在這種情況下可能是一個不成熟和不必要的優化。 –

+0

情況並非如此,因爲mkdir是原子的,即它相當於一個'測試和設置'操作。使用非原子測試和集合操作編碼LBYL操作將導致競爭條件,但這是一個實現錯誤。 – RichVel