Большой файл не сбрасывается на диск сразу после вызова 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
.
Ответы
Ответ 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)