Большой файл не сбрасывается на диск сразу после вызова close()? - программирование
Подтвердить что ты не робот

Большой файл не сбрасывается на диск сразу после вызова close()?

Я создаю большой файл с моим python script (более чем 1GB, на самом деле там 8 из них). Сразу после создания я должен создать процесс, который будет использовать эти файлы.

script выглядит следующим образом:

# This is more complex function, but it basically does this:
def use_file():
    subprocess.call(['C:\\use_file', 'C:\\foo.txt']);


f = open( 'C:\\foo.txt', 'wb')
for i in 10000:
    f.write( one_MB_chunk)
f.flush()
os.fsync( f.fileno())
f.close()

time.sleep(5) # With this line added it just works fine

t = threading.Thread( target=use_file)
t.start()

Но приложение use_file действует как foo.txt пусто. Происходят какие-то странные вещи:

  • если я выполнил C:\use_file C:\foo.txt в консоли (после завершения script), я получаю правильные результаты
  • если я выполняю вручную use_file() в другой консоли python, я получаю правильные результаты
  • C:\foo.txt отображается на диске сразу после вызова open(), но остается размер 0B до конца script
  • Если я добавлю time.sleep(5), он просто начнет работать как ожидалось (или, скорее, требуется)

Я уже нашел:

  • os.fsync(), но он не работает (результат из use_file выглядит так, как будто C:\foo.txt был пустым)
  • Использование buffering=(1<<20) (при открытии файла) не работает либо

Мне все больше и больше нравится это поведение.

Вопросы:

  • Работает ли python fork close() на фоне? Где это документировано?
  • Как это сделать?
  • Я что-то пропустил?
  • После добавления sleep: это ошибка windows/python?

Примечания: (для случая, когда что-то не так с другой стороны) приложение use_data использует:

handle = CreateFile("foo.txt", GENERIC_READ, FILE_SHARE_READ, NULL,
                               OPEN_EXISTING, 0, NULL);
size = GetFileSize(handle, NULL)

И затем обрабатывает size байты из foo.txt.

4b9b3361

Ответ 1

f.close() вызывает f.flush(), который отправляет данные в ОС. Это не обязательно записывает данные на диск, потому что ОС буферизует его. Как вы правильно поработали, если вы хотите заставить ОС записать его на диск, вам нужно os.fsync().

Считаете ли вы, что просто передаете данные непосредственно в use_file?


EDIT: вы говорите, что os.fsync() "не работает". Чтобы уточнить, если вы делаете

f = open(...)
# write data to f
f.flush()
os.fsync(f.fileno())
f.close()

import pdb; pdb.set_trace()

а затем посмотрите на файл на диске, есть ли у него данные?

Ответ 2

Изменить: обновлено с информацией, относящейся к Python 3.x

Существует супер-старый отчет об ошибке, в котором обсуждалась подозрительно подобная проблема на https://bugs.python.org/issue4944. Я сделал небольшой тест, который показывает ошибку: https://gist.github.com/estyrke/c2f5d88156dcffadbf38

После получения замечательного объяснения от пользователя eryksun по ссылке выше, я теперь понимаю, почему это происходит, и это не ошибка сама по себе. Когда дочерний процесс создается в Windows, по умолчанию он наследует все открытые дескрипторы файлов из родительского процесса. Так что вы видите, вероятно, на самом деле нарушение обмена, потому что файл, который вы пытаетесь прочитать в дочернем процессе, открыт для записи через унаследованный дескриптор в другом дочернем процессе. Возможная последовательность событий, которая вызывает это (с использованием примера воспроизведения в Gist выше):

Thread 1 opens file 1 for writing
  Thread 2 opens file 2 for writing
  Thread 2 closes file 2
  Thread 2 launches child 2
  -> Inherits the file handle from file 1, still open with write access
Thread 1 closes file 1
Thread 1 launches child 1
-> Now it can't open file 1, because the handle is still open in child 2
Child 2 exits
-> Last handle to file 1 closed
Child 1 exits

Когда я компилирую простую C-дочернюю программу и запускаю script на моей машине, она не работает, по крайней мере, в одном из потоков большую часть времени с Python 2.7.8. С Python 3.2 и 3.3 тест script без перенаправления не прерывается, потому что значение по умолчанию аргумента close_fds для subprocess.call теперь True, когда перенаправление не используется. Другой тестер script с использованием перенаправления все еще не работает в этих версиях. В Python 3.4 оба теста успешны, из-за PEP 446, который по умолчанию делает все файлы без наследования.

Заключение

Порождение дочернего процесса из потока в Python означает, что ребенок наследует все открытые дескрипторы файлов, даже из других потоков, чем тот, где порожден ребенок. Это, по крайней мере, для меня, не особенно интуитивно.

Возможные решения:

  • Перейдите на Python 3.4, где по умолчанию файлы не наследуются.
  • Передать close_fds=True в subprocess.call, чтобы полностью отключить наследование (это значение по умолчанию в Python 3.x). Обратите внимание, что это предотвращает перенаправление дочернего процесса "стандартный ввод/вывод/ошибка".
  • Перед созданием новых процессов убедитесь, что все файлы закрыты.
  • Используйте os.open для открытия файлов с флагом os.O_NOINHERIT в Windows.
    • tempfile.mkstemp также использует этот флаг.
  • Вместо этого используйте win32api. Передача указателя NULL для параметра lpSecurityAttributes также предотвращает наследование дескриптора:

    from contextlib import contextmanager
    import win32file
    
    @contextmanager
    def winfile(filename):
        try:
            h = win32file.CreateFile(filename, win32file.GENERIC_WRITE, 0, None, win32file.CREATE_ALWAYS, 0, 0)
            yield h
        finally:
            win32file.CloseHandle(h)
    
    with winfile(tempfilename) as infile:
        win32file.WriteFile(infile, data)