Перенаправленный вывод из вызова подпроцесса теряется?
У меня есть некоторый код Python, который выглядит примерно так, используя некоторые библиотеки, которые вы можете иметь или не иметь:
# Open it for writing
vcf_file = open(local_filename, "w")
# Download the region to the file.
subprocess.check_call(["bcftools", "view",
options.truth_url.format(sample_name), "-r",
"{}:{}-{}".format(ref_name, ref_start, ref_end)], stdout=vcf_file)
# Close parent process's copy of the file object
vcf_file.close()
# Upload it
file_id = job.fileStore.writeGlobalFile(local_filename)
По сути, я запускаю подпроцесс, который должен загрузить некоторые данные для меня и распечатать их в стандартном формате. Я перенаправляю эти данные в файл, а затем, как только возвращается вызов подпроцесса, я закрываю свой дескриптор файла и затем копирую файл в другое место.
Я наблюдаю, что иногда ожидаемый конец данных не превращается в копию. Теперь возможно, что bcftools
просто иногда не записывает эти данные, но я беспокоюсь, что могу сделать что-то небезопасное и каким-то образом получить доступ к файлу после subprocess.check_call()
вернулся, но до того, как данные, которые дочерний процесс записывает в стандартный вывод, попадают на диск, где я могу их увидеть.
Если посмотреть на стандарт C (поскольку bcftools реализован на C/C++), то, когда программа выходит из программы нормально, все открытые потоки (включая стандартный вывод) сбрасываются и закрываются. Увидеть [lib.support.start.term]
раздел здесь, описывающий поведение exit()
, который вызывается неявно, когда main()
возвращает:
- Далее, все открытые потоки C (как опосредовано сигнатурами функций, объявленными в) с неписанными буферизованными данными сбрасываются, все открытые потоки C закрываются, а все файлы, созданные с помощью вызова tmp- file(), удаляются. 30)
- Наконец, управление возвращается в среду хоста. Если статус равен нулю или EXIT_SUCCESS, возвращается определяемая реализацией форма успешного завершения статуса. Если статус - EXIT_FAILURE, возвращается определяемая реализацией форма неудачного завершения статуса. В противном случае возвращаемый статус определяется реализацией.31)
Поэтому, прежде чем дочерний процесс завершается, он закрывает (и, следовательно, сбрасывает) стандартный вывод.
Тем не менее, страница руководства для Linux close(2)
отмечает, что закрытие файлового дескриптора не обязательно гарантирует, что любые записанные в него данные действительно попали на диск:
Успешное закрытие не гарантирует, что данные были успешно сохранены на диск, поскольку ядро откладывает запись. Обычно файловая система не очищает буферы при закрытии потока. Если вам нужно убедиться, что данные физически хранятся, используйте fsync(2). (Это будет зависеть от аппаратного обеспечения диска в этот момент.)
Таким образом, может показаться, что при выходе из процесса его стандартный поток вывода сбрасывается, но если этот поток фактически поддерживается дескриптором файла, указывающим на файл на диске, запись на диск не гарантируется завершенной. Я подозреваю, что это может быть то, что здесь происходит.
Итак, мои актуальные вопросы:
Мое чтение спецификаций правильно? Может ли дочерний процесс показаться родительскому процессу завершенным до того, как его перенаправленный стандартный вывод будет доступен на диске?
Можно ли как-то подождать, пока все данные, записанные дочерним процессом в файлы, будут на самом деле синхронизированы с диском операционной системой?
Должен ли я звонить
flush()
или какая-то версия Pythonfsync()
на копию родительского процесса файлового объекта? Может ли эта принудительная запись в один и тот же файловый дескриптор дочерними процессами быть зафиксированной на диске?
1 ответ
Да, могут быть минуты, прежде чем данные будут записаны на диск (физически). Но вы можете прочитать это задолго до этого.
Если вы не беспокоитесь о сбое питания или панике ядра; не имеет значения, находятся ли данные на диске. Важная часть, считает ли ядро, что данные записаны.
Это безопасно читать из файла, как только check_call()
возвращается. Если вы не видите все данные; это может указывать на ошибку в bcftools
или это writeGlobalFile()
не загружает все данные из файла. Вы можете попытаться обойти первый, отключив режим буферизации блоков для bsftools
'stdout ( укажите псевдо-tty, используйте unbuffer
утилита командной строки и т. д.).
Q: Правильно ли я прочитал спецификации? Может ли дочерний процесс показаться родительскому процессу завершенным до того, как его перенаправленный стандартный вывод будет доступен на диске?
да. да.
В: Можно ли как-то подождать, пока все данные, записанные дочерним процессом в файлы, будут фактически синхронизированы с диском операционной системой?
нет. fsync()
недостаточно в общем случае. Вероятно, вам все равно это не нужно (чтение данных - это другая проблема, а не проверка того, что они записаны на диск).
В: Должен ли я вызывать flush() или какую-то версию fsync() на Python для копии файлового объекта родительского процесса? Может ли эта принудительная запись в один и тот же дескриптор файла дочерними процессами быть зафиксированной на диске?
Это было бы бессмысленно. .flush()
очищает буферы, которые являются внутренними для родительского процесса (вы можете использовать open(filename, 'wb', 0)
чтобы избежать создания ненужных буферов в родительском).
fsync()
работает с файловым дескриптором (у ребенка есть собственный файловый дескриптор). Я не знаю, использует ли ядро разные буферы для разных файловых дескрипторов, ссылающихся на один и тот же файл на диске. Опять же, это не имеет значения - если вы наблюдаете отсутствие данных (без сбоев); fsync()
здесь не поможет
Вопрос: Просто чтобы прояснить, я вижу, что вы утверждаете, что данные действительно должны быть доступны для чтения другим процессам, потому что соответствующие буферы ОС совместно используются процессами. Но каков ваш источник этого утверждения? Есть ли место в спецификации или документации Linux, на которое вы можете указать, чтобы гарантировать, что эти буферы используются совместно?
Ищите "После write()
чтобы обычный файл успешно вернулся ":
Любой успешный
read()
из каждой позиции байта в файле, который был изменен этой записью, должны возвращаться данные, указанныеwrite()
для этой позиции, пока такие байтовые позиции снова не будут изменены.