Pergunta

Eu tenho um módulo de cache de urllib2, que trava esporadicamente por causa do seguinte código:

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

O problema é que, quando a segunda linha está sendo executada, a pasta pode existir e errará:

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

Isso ocorre porque o script é lançado simultaneamente várias vezes, por código de terceiros sobre o qual não tenho controle.

O código (antes de tentar corrigir o bug) pode ser encontrado Aqui, no Github

Eu não posso usar o tempfile.mkstemp, como resolve a condição de corrida usando um diretório nomeado aleatoriamente (Fonte tempfile.py aqui), que derrotaria o objetivo do cache.

Não quero simplesmente descartar o erro, pois o mesmo erro errno 17 é levantado se o nome da pasta existir como um arquivo (um erro diferente), por exemplo:

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

Eu não posso usar threading.RLock como o código é chamado de vários processos.

Então, tentei escrever um bloqueio simples baseado em arquivos (Essa versão pode ser encontrada aqui), mas isso tem um problema: cria o arquivo de bloqueio um de nível, então /tmp/example.lock por /tmp/example/, que quebra se você usa /tmp/ como um diretor de cache (como tenta fazer /tmp.lock)..

Em suma, eu preciso armazenar em cache urllib2 respostas ao disco. Para fazer isso, preciso acessar um diretório conhecido (criando -o, se necessário), de maneira segura multiprocesso. Ele precisa funcionar no OS X, Linux e Windows.

Pensamentos? A única solução alternativa em que consigo pensar é reescrever o módulo de cache usando o armazenamento SQLITE3, em vez de arquivos.

Foi útil?

Solução

No Python 3.x, você pode usar os.makedirs(path, exists_ok=True), o que não levantará nenhuma exceção se esse diretório existir. Ele irá aumentar FileExistsError: [Errno 17] Se existir um arquivo com o mesmo nome do diretório solicitado (path).

Verifique com:

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)

Outras dicas

Ao invés de

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

você poderia fazer

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

Como você acabaria com o mesmo funcionalidade.

Isenção de responsabilidade: não sei o quão pitônico isso pode ser.


Usando SQLite3, poderia ser um pouco de exagero, mas adicionaria um muito de funcionalidade e flexibilidade no seu caso de uso.

Se você tiver que fazer muitas "selecionando", inserção e filtragem simultâneas, é uma ótima ideia usar SQLite3, pois não adicionará muita complexidade sobre arquivos simples (pode -se argumentar que ele remove a complexidade).


Relendo sua pergunta (e comentários), posso entender melhor o seu problema.

Qual é a possibilidade de que um Arquivo poderia criar a mesma condição de corrida?

Se for pequeno o suficiente, então eu faria algo como:

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

Além disso, lendo seu código, eu mudaria

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

para

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

Como é realmente o que você quer, Python para reorrair exatamente a mesma exceção (apenas nitpicking).


Mais uma coisa, pode ser isto Pode ser útil para você (apenas como UNIX).

O código com quem acabei foi:

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

Você poderia pegar a exceção e depois testar se o arquivo existe como um diretório ou não?

Quando você tem condições de corrida, muitas vezes o EAFP (mais fácil de perguntar perdão do que permissão) funciona melhor que o LBYL (olhe antes de saltar)

Estratégias de verificação de erros

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top