Назад | Перейти на главную страницу

почему файл иногда кажется пустым при записи другим процессом

У нас есть ситуация, когда файлы загружаются в папку по FTP, а затем обслуживаются nginx. Мы обнаружили, что если запрос GET следует сразу за изменением файла, nginx возвращает файлы с 0 байтами.

Пытаясь отладить эту проблему, я написал 2 сценария Python, чтобы посмотреть, могу ли я воспроизвести ошибку простым способом.

Первый пишет в файл

  while True:
      with open('testfile' , 'w') as f:
          f.write("test")

А второй гласит

  while True:
      with open('testfile' , 'r') as cf:
          print(cf.read())

при запуске этих файлов в 2 отдельных процессах вывод читателя будет либо «тестовым», либо «», что означает, что иногда файл кажется читателю пустым. Кажется, это не связано с реализацией python, поскольку я могу воспроизвести эффект с помощью bash следующим образом:

(writer.sh)

  while true; do
      echo test > testfile
  done

(reader.sh)

  while true; do
      cat testfile
      printf "\n"
  done

Файловая система - ext4, ОС - Ubuntu 16.04.

Так:

Почему читатель иногда видит пустой файл (примерно в 50% случаев)?

Почему мы никогда не видим частичную запись («тэ», «тэс» и т. Д.)?

Заранее спасибо за вашу помощь.

Престижность, вы только что открыли для себя буферизацию файлов. При записи на диск вы можете использовать либо буферизованную запись, либо прямую запись ввода-вывода. По соображениям производительности большая часть программного обеспечения (включая интерпретатор Python) по умолчанию использует буферизованную запись. Если вам нужно выполнить прямой ввод-вывод, есть хороший модуль Python с метко названным Directio это делает именно это.

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

Другие описали, как это буферизованный ввод-вывод, когда вы видите усеченный файл до того, как его содержимое было сброшено.

Еще несколько подробностей о нескольких способах решения этой проблемы:

Загрузите файлы во временный каталог в той же файловой системе, что и цель, затем mv на место. Переименование - это атомарная операция, поэтому читатели увидят только старый или новый файл, а не что-то среднее. Однако ядро ​​все еще может завершить запись на диск по своему расписанию, если приложение не вызовет fsync (). Закрытие файла или ожидание произвольного времени не надежно привести к тому, что файл будет на диске.

Или измените приложение, чтобы оно поддерживалось базой данных. Пусть база данных обеспечивает единообразное представление документа в памяти и в хранилище, вот что они делают. Возможно, это не стоит усилий по реализации, если единственная причина - избавиться от очень маленького окна несогласованности.

Скорее всего, вы столкнулись с гонкой, когда:

  • запись обрезает файл из-за перенаправления (">").
  • файл читается читателем (пустой файл).
  • файл написан писателем.

Если вы поместите цикл записи на короткий период сна, вы увидите это гораздо реже.

Вы можете избежать этого, используя атомарное действие для создания файла, например:

while true do;
    echo test > file.tmp
    mv file.tmp testfile
done

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

Файлы, как правило, записываются в виде блоков (подмножеств полных данных), размер этих блоков определяется комбинацией используемой функции и доступных системных ресурсов, поэтому ОС обычно пытается оптимизировать размер блока. Что происходит, так это то, что блок будет записан в ОЗУ перед записью на диск, а затем полный блок будет записан на диск сразу без времени для чтения во время записи этого блока. Это приводит к гораздо более быстрой записи, чем это было возможно в противном случае.

В вашем случае, когда вы пишете слово «тест», он будет меньше любого размера блока, который выбирает ОС, поэтому все будет записано сразу. Для вашего теста вы должны написать гораздо больший тест и, вероятно, установить размер блока (хотя лучше позволить ОС решать большую часть времени).

Я подозреваю, что в вашем тесте происходит то, что половину времени вы ловите пустой файл до того, как он записывается, а в другой половине он ловит его после записи блока. Если вы попытаетесь записать объем данных, превышающий размер вашего блока, я думаю, вы увидите частично записанные файлы.