在Python中创建竞争条件文件夹

dbr*_*dbr 16 python caching race-condition

我有一个urllib2缓存模块,由于以下代码,它偶尔会崩溃:

if not os.path.exists(self.cache_location):
    os.mkdir(self.cache_location)
Run Code Online (Sandbox Code Playgroud)

问题是,到第二行执行时,文件夹可能存在,并将出错:

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

这是因为脚本同时启动了很多次,由第三方代码我无法控制.

代码(在我尝试修复bug之前)可以在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因为代码是从多个进程调用的.

所以,我尝试编写一个简单的基于文件的锁(这个版本可以在这里找到),但是这有一个问题:它创建一个级别的锁文件,所以/tmp/example.lock对于/tmp/example/,如果你/tmp/用作缓存目录,它会中断(因为它尝试制作/tmp.lock)..

简而言之,我需要缓存urllib2对光盘的响应.为此,我需要以多进程安全的方式访问已知目录(如果需要,创建它).它需要在OS X,Linux和Windows上运行.

思考?我能想到的唯一替代解决方案是使用SQLite3存储而不是文件重写缓存模块.

Est*_*ber 11

代替

if not os.path.exists(self.cache_location):
    os.mkdir(self.cache_location)
Run Code Online (Sandbox Code Playgroud)

你能做到的

try:
    os.makedirs(self.cache_location)
except OSError:
    pass
Run Code Online (Sandbox Code Playgroud)

因为你最终会得到相同的功能.

免责声明:我不知道Pythonic会是怎样的.


使用SQLite3,可能有点矫枉过正,但会为您的用例添加许多功能和灵活性.

如果你必须做很多"选择",并发插入和过滤,那么使用它是一个好主意SQLite3,因为它不会在简单文件上添加太多复杂性(可以说它消除了复杂性).


重读你的问题(和评论)我可以更好地理解你的问题.

文件可能创建相同竞争条件的可能性是多少?

如果它足够小,那么我会做类似的事情:

if not os.path.isfile(self.cache_location):
    try:
        os.makedirs(self.cache_location)
    except OSError:
        pass
Run Code Online (Sandbox Code Playgroud)

另外,阅读你的代码,我会改变

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

else:
    # Our target dir is already a file, or different error,
    # relay the error!
    raise
Run Code Online (Sandbox Code Playgroud)

因为它真的是你想要的,Python重新加入完全相同的异常(只是挑剔).


还有一两件事,可能是可能对你有用的(类似Unix只).

  • @dbr:如果要区分"目录已存在为目录"和"目录名称已作为文件存在",则在except子句中执行此操作.不要让这太复杂.只需处理异常. (2认同)

dbr*_*dbr 10

我最终得到的代码是:

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
Run Code Online (Sandbox Code Playgroud)


ika*_*men 3

在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)
Run Code Online (Sandbox Code Playgroud)