2009-10-19 38 views
13

Tôi có một mô-đun urllib2 bộ nhớ đệm, mà thỉnh thoảng bị treo vì đoạn mã sau:Race điều kiện tạo thư mục trong Python

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

Vấn đề là, vào thời điểm dòng thứ hai đang được thực hiện, các thư mục có thể tồn tại và sẽ báo lỗi:

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

Điều này là do tập lệnh được khởi chạy đồng thời nhiều lần, bởi mã của bên thứ ba mà tôi không kiểm soát được.

Mã này (trước khi tôi đã cố gắng để sửa chữa các lỗi) có thể được tìm thấy here, on github

tôi không thể sử dụng tempfile.mkstemp, vì nó giải quyết được tình trạng chủng tộc bằng cách sử dụng một thư mục có tên ngẫu nhiên (tempfile.py source here), mà sẽ đánh bại mục đích của bộ nhớ cache.

Tôi không muốn chỉ đơn giản là loại bỏ các lỗi, như lỗi tương tự Errno 17 lỗi được nâng lên nếu tên thư mục tồn tại như một tập tin (một lỗi khác nhau), ví dụ:

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

tôi không thể sử dụng threading.RLock vì mã được gọi từ nhiều quy trình.

Vì vậy, tôi cố gắng viết một khóa tập tin dựa trên đơn giản (that version can be found here), nhưng điều này có một vấn đề: nó tạo ra lockfile lên mức trên, vì vậy /tmp/example.lock cho /tmp/example/, mà phá vỡ nếu bạn sử dụng /tmp/ như một dir cache (như nó cố gắng để làm cho /tmp.lock) ..

Tóm lại, tôi cần phải lưu vào bộ nhớ cache urllib2 câu trả lời cho đĩa. Để thực hiện điều này, tôi cần truy cập vào một thư mục đã biết (tạo nó, nếu được yêu cầu), theo cách an toàn đa xử lý. Nó cần phải làm việc trên OS X, Linux và Windows.

Suy nghĩ? Giải pháp thay thế duy nhất tôi có thể nghĩ đến là viết lại mô đun bộ nhớ cache bằng cách sử dụng bộ nhớ SQLite3, chứ không phải là tệp.

Trả lời

5

Mã tôi đã kết thúc với là:

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

Bạn có thể bắt ngoại lệ và sau đó kiểm tra xem tệp có tồn tại dưới dạng thư mục hay không?

+0

Có thể! Tôi nghĩ về điều đó giống như tôi đang đọc lại câu hỏi, trước khi gửi nó .. Tôi đã thực hiện điều này (http://github.com/dbr/tvdb_api/blob/468d9f816373b14ef3a483fca07e031b69fa62f9/cache.py#L103-114), và sẽ nhận được người báo cáo lỗi để kiểm tra nó ngay. – dbr

+0

Điều này dường như hoạt động hoàn hảo, cảm ơn! – dbr

+1

@dbr: lưu ý rằng trên dòng 114, bạn muốn 'tăng e' vì nó đã là một thể hiện của' OSError'. http://github.com/dbr/tvdb_api/blob/468d9f816373b14ef3a483fca07e031b69fa62f9/cache.py#L114 – nosklo

11

Thay vì

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

bạn có thể làm

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

Như bạn sẽ kết thúc với cùng chức năng.

KHUYẾN CÁO: Tôi không biết Pythonic có thể như thế nào.


Sử dụng SQLite3, thể là một chút quá mức cần thiết, nhưng sẽ thêm một nhiều chức năng và linh hoạt để trường hợp sử dụng của bạn.

Nếu bạn phải thực hiện rất nhiều "lựa chọn", chèn và lọc đồng thời, bạn nên sử dụng SQLite3, vì nó sẽ không thêm quá phức tạp so với tệp đơn giản (có thể lập luận rằng nó loại bỏ sự phức tạp).


Đọc lại câu hỏi của bạn (và nhận xét) Tôi có thể hiểu rõ hơn về vấn đề của bạn.

Khả năng một tệp có thể tạo điều kiện chủng tộc giống nhau là gì?

Nếu nó đủ nhỏ, sau đó tôi muốn làm điều gì đó như:

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

Ngoài ra, đọc mã của bạn, tôi muốn thay đổi

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 

vì nó thực sự là những gì bạn muốn, Python để khắc phục chính xác cùng một ngoại lệ (chỉ cần nitpicking).


Một điều nữa, có thể là this có thể được sử dụng cho bạn (chỉ giống Unix).

+2

+1: Hoàn toàn là Pythonic, IMO. –

+1

Đồng ý, mặc dù bạn có thể xem xét sử dụng 'os.makedirs' thay thế, cũng tạo thư mục cha, nếu cần. –

+0

@Eli Courtwright: điểm tốt. Đã sửa đổi. – voyager

2

Khi bạn có điều kiện chủng tộc thường EAFP (dễ dàng hơn để cầu xin tha thứ hơn cho phép) hoạt động tốt hơn mà LBYL (tìm kiếm trước khi bạn nhảy)

Error checking strategies

+0

Tất cả điều này phụ thuộc vào khả năng xung đột - EAFP = đồng thời lạc quan, kiểm tra xung đột sau khi thử hoạt động, và hoạt động tốt nếu có xác suất nhỏ xung đột. LBYL = đồng thời bi quan kiểm tra trước khi hoạt động và tốt hơn nếu có nhiều xung đột thường. Đối với một trường hợp đơn giản như thế này, tôi nghĩ bi quan là tốt hơn. – RichVel

+0

@RichVel, với LBYL ở đây bạn có khả năng xảy ra tình trạng đua. Vì vậy, bạn cần thêm mã để xử lý ngoại lệ. Vì vậy, nó chỉ là một hoạt động đơn giản và mã bổ sung không phải là nhiều bây giờ, nhưng những điều này có một cách để phát triển trong suốt cuộc đời của dự án. Có một khoản phí thêm mỗi lần ai đó cần đọc/hiểu/gỡ lỗi mã bổ sung này. Tôi nghĩ rằng đó có thể là một tối ưu hóa sớm và không cần thiết trong trường hợp này. –

+0

Đó không phải là trường hợp, bởi vì mkdir là nguyên tử tức là nó tương đương với một hoạt động 'thử nghiệm và thiết lập' duy nhất. Mã hóa hoạt động LBYL với thử nghiệm phi nguyên tử và các hoạt động thiết lập sẽ gây ra các điều kiện chủng tộc, nhưng đó là lỗi triển khai. – RichVel