Странное состояние гонки: FileNotFoundError с mkdtemp

Я получаю довольно странное состояние гонки в Mac OS X с Python (я тестировал только Python 3.3). Я делаю несколько временных каталогов, записываю в них что-то, а затем очищаю их. Что-то в духе

while running:
    (do something)
    tempdir = mkdtemp('name')
    try:
        (write some stuff to tempdir)
    finally:
        shutil.rmtree(tempdir)

Однако в некоторых из более поздних циклов (write some stuff to tempdir) я получаю такие ошибки, как

    with open(os.path.join("/var/folders/yc/8wpl9rlx47qgzxqpcf003k280000gn/T/tmp0fh2ztname", "file"), 'w', encoding='utf-8') as fn:
FileNotFoundError: [Errno 2] No such file or directory: '/var/folders/yc/8wpl9rlx47qgzxqpcf003k280000gn/T/tmpups5dpname/file'

(Для ясности я указал путь к временному каталогу)

Обратите внимание, что открываемый путь отличается от пути, который он не может найти. В каждом случае путь в сообщении об ошибке — это временный каталог из предыдущей итерации цикла.

Ошибка воспроизводится большую часть времени в одном и том же месте (примерно после четвертой итерации), но не каждый раз.

РЕДАКТИРОВАТЬ: я только что понял, что это, вероятно, актуально. Материал (write some stuff to tempdir) на самом деле происходит в подпроцессе. Вот как я уверен в пути tempdir, я должен передать его подпроцессу (на самом деле я солгал о бите «ясности», я на самом деле пишу файл Python с этой точной строкой with open). Так я точно знаю, что путь tempdir действительно отличается от используемого.


person asmeurer    schedule 25.03.2014    source источник
comment
Вы уверены в пути? Как насчет того, чтобы выполнить os.path.join над with, поймать ошибку и распечатать путь? Ошибки кодирования более вероятны, чем фундаментальные языковые ошибки!   -  person tdelaney    schedule 25.03.2014
comment
Я только что добавил еще немного информации. На самом деле, теперь, когда я об этом думаю, возможно, я не могу доверять выводу трассировки. Возможно, состояние гонки возникает из-за чтения кэшированного файла pyc (я пишу в один и тот же файл в каждом экземпляре).   -  person asmeurer    schedule 25.03.2014
comment
Вы можете запустить Python, используя python -c [code]. В подпроцессе довольно легко просто передать произвольный блок кода (включая новые строки и отступы). Это позволит избежать необходимости создавать файлы Python на диске и рисковать какой-либо файловой системой или гонкой кэширования байт-кода Python.   -  person nneonneo    schedule 25.03.2014


Ответы (1)


Я понял. Оказывается, это не имеет ничего общего с mkdtemp (вздох облегчения, что Mac OS X и Python делают правильные вещи).

Проблема в том, что я записывал код в файл, включая бит with open(os.path.join("/var/folders/yc/8wpl9rlx47qgzxqpcf003k280000gn/T/tmp0fh2ztname", "file"), 'w', encoding='utf-8') as fn:, и запускал его в подпроцессе. Проблема заключалась в том, что я каждый раз использовал один и тот же файл, а файлы .pyc неправильно аннулировались.

Сообщение об ошибке сбивало с толку, потому что, когда Python генерирует трассировку, он считывает файл .py (где фактический код), но на самом деле запускается файл .pyc.

Если я правильно понимаю http://nedbatchelder.com/blog/200804/the_structure_of_pyc_files.html , временные метки в .pyc файлах имеют гранулярность в одну секунду (это объясняет, почему это каждый раз воспроизводилось в одном и том же месте: один и тот же четвертый элемент в цикле выполнялся менее чем за секунду).

Решение состояло в том, чтобы явно удалить файлы .pyc при записи файла (в других обстоятельствах вы также могли записать в сам временный файл, но в моем случае мне нужно было, чтобы файл можно было импортировать под тем же именем).

Что-то в духе

if sys.version_info >= (3,):
    os.unlink(os.path.join(path_to_file, '__pycache__', 'file.cpython-%s%s.pyc' % sys.version_info[:2]))
    os.unlink(os.path.join(path_to_file, '__pycache__', 'file.cpython-%s%s.pyo' % sys.version_info[:2]))
else:
    os.unlink(os.path.join(path_to_file, 'file.pyc'))
    os.unlink(os.path.join(path_to_file, 'file.pyo'))
person asmeurer    schedule 25.03.2014
comment
+1 Интересный случай, может быть, вы могли бы принять свой собственный ответ, поэтому вопрос будет отображаться как решенный в поиске SO. - person Roberto; 27.03.2014
comment
Да, я планировал. SO позволяет вам принять свой собственный ответ только через 48 часов. - person asmeurer; 28.03.2014