Отсутствующие события inotify (в каталоге .git)

11

Я наблюдаю за изменениями в файлах, используя события inotify (как это происходит из Python, вызывая libc).

Для некоторых файлов во время a git cloneя вижу что-то странное: я вижу IN_CREATEсобытие и вижу, lsчто в файле есть контент, однако я никогда не вижу IN_MODIFYили IN_CLOSE_WRITE. Это вызывает у меня проблемы, так как я хотел бы ответить IN_CLOSE_WRITEна файлы: в частности, чтобы начать загрузку содержимого файла.

Файлы, которые ведут себя странно, находятся в .git/objects/packкаталоге, и они заканчиваются на .packили .idx. Другие файлы, которые создает git, имеют более регулярную цепочку IN_CREATE-> IN_MODIFY-> IN_CLOSE_WRITE(я не наблюдаю за IN_OPENсобытиями).

Это внутри докера на MacOS, но я видел доказательства того же на докере на Linux в удаленной системе, поэтому я подозреваю, что аспект MacOS не имеет значения. Я вижу это, если смотрю и git cloneв том же контейнере докера.

Мои вопросы:

  • Почему эти события отсутствуют в этих файлах?

  • Что с этим можно сделать? В частности, как я могу ответить на завершение записи в эти файлы? Примечание: в идеале я хотел бы ответить, когда запись «закончена», чтобы избежать ненужной / (неправильной) загрузки «незаконченной» записи.


Редактировать: чтение https://developer.ibm.com/tutorials/l-inotify/ похоже, что то, что я вижу, соответствует

  • отдельный временный файл с именем, как tmp_pack_hBV4Alz, создается, изменяется и закрывается;
  • на этот файл создается жесткая ссылка с окончательным .packименем;
  • оригинальное tmp_pack_hBV4Alzимя удалено.

Я думаю, что моя проблема, которая заключается в попытке использовать inotify в качестве триггера для загрузки файлов, сводится к тому, чтобы заметить, что .packфайл представляет собой жесткую ссылку на другой файл, и к загрузке в этом случае?

Михал Чарамза
источник
Ответ может быть где-то здесь ...
Чороба
@choroba Возможно, вы правы ... Я вижу много ссылок на mmap, а inotify не сообщает о доступе mmap к файлам
Михал Чарамза
1
Кстати, что за оригинальную проблему вы пытаетесь решить (с помощью inotify)? Может быть, существует более надежное решение, которое пытается угадать, что процесс Git делает / сделал с хранилищем?
kostix
@kostix Это часть github.com/uktrade/mobius3 , синхронизирующая домашние папки пользователей из контейнеров с JupyterLab или RStudio в AWS Fargate, и из S3, и в этих домашних папках могут быть папки .git. Я знаю, что решение inotify никогда не будет «надежным», но я надеюсь, что оно может быть «достаточно надежным».
Михал Чарамза
1
@tink Похоже, что принятый ответ - это патч для ядра Linux? Я подозреваю, что в целом это сработает, но в моем случае с Фаргейтом у меня нет такого контроля. (И я признаю, что немного боюсь последствий зависимости от исправленного ядра в долгосрочной перспективе, даже если бы у меня была такая сила ...)
Михал Чарамза

Ответы:

5

Чтобы ответить на ваш вопрос отдельно для git2.24.1 в Linux 4.19.95:

  • Почему эти события отсутствуют в этих файлах?

Вы не видите IN_MODIFY/ IN_CLOSE_WRITEevents, потому git cloneчто всегда будете пытаться использовать жесткие ссылки для файлов в .git/objectsкаталоге. При клонировании по сети или через границы файловой системы эти события будут появляться снова.

  • Что с этим можно сделать? В частности, как я могу ответить на завершение записи в эти файлы? Примечание: в идеале я хотел бы ответить, когда запись «закончена», чтобы избежать ненужной / (неправильной) загрузки «незаконченной» записи.

Чтобы поймать модификацию жестких ссылок, вы должны установить обработчик для CREATEсобытия inotify, которое следует и отслеживает эти ссылки. Обратите внимание, что простой CREATEможет также означать, что был создан непустой файл. Затем в IN_MODIFY/ IN_CLOSE_WRITEк любому из файлов вы должны запустить одно и то же действие для всех связанных файлов. Очевидно, вы также должны удалить эти отношения на DELETEмероприятии.

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


коррекция

После gitтщательной проверки исходного кода и работы gitс ним straceя обнаружил, что gitфайлы, отображаемые в память , действительно используются, но в основном для чтения содержимого. Смотрите использование xmmapкоторого всегда вызывается PROT_READтолько с. , Поэтому мой предыдущий ответ ниже НЕ является правильным ответом. Тем не менее, в информационных целях я все еще хотел бы сохранить это здесь:

  • Вы не видите IN_MODIFYсобытия, потому что packfile.cиспользует mmapдля доступа к файлу и inotifyне сообщает об изменениях для mmapфайлов ed.

    Из справочной страницы inotify :

    API inotify не сообщает о доступе к файлам и изменениях, которые могут произойти из-за mmap (2), msync (2) и munmap (2).

Ente
источник
Мой механизм обнаружения изменений зависит от того IN_CLOSE_WRITE, что, я думаю, будет по-прежнему срабатывать при закрытии файла, который был записан для использования mmap, поскольку файл должен был быть открыт в режиме записи?
Михал Чарамза
Я должен исследовать это, но я подозреваю, что отображенный в память файл вообще не вызывает никаких событий inotify. Большинство событий inify связаны с состоянием дескриптора файла, но когда вы создаете mmapфайл, все может выйти из строя. Например, вы все равно можете записывать в закрытый файловый дескриптор, когда файл отображается в памяти.
Ente
Поцарапайте это, я только что проверил этот пример реализации, и я получаю CLOSE_WRITE_CLOSEдаже, если я удаляю closeи munmapв конце. Приходится копать глубже в фактическую реализацию git ..
Ente
Хм, я изо всех сил пытаюсь воспроизвести вашу проблему. В моих тестах с inotifywaitи git clone(2.24.1) я получаю OPEN-> CLOSE_NOWRITE,CLOSEдля *.idxфайлов. Может быть, вы забыли настроить обработчик для CLOSE_NOWRITE,CLOSE? Примечание: вы получите, *NOWRITE*потому что все записи происходили через отображенную память.
Ente
Да, есть CLOSE_NOWRITE: проблема в том, что я не вижу IN_CLOSE_WRITE, и я хотел бы ответить на «изменения» файла, чтобы инициировать загрузку, но проигнорировать «чтение» файла. Обратите внимание, я на самом деле думаю, что сейчас ограничение mmap + inotify является чем-то вроде красной селедки. Я думаю, проблема заключается в том, что файлы .pack/ .idxизначально создавались как жесткие ссылки на другой файл, и поэтому запускаются только IN_CREATEOPEN-> CLOSE_NOWRITEпроисходит позже, когда git фактически читает файлы).
Михал Чарамза
2

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

  1. Содержимое файла читается в память (и изменяется).
  2. Измененное содержимое записывается в отдельный файл (обычно расположенный в том же каталоге, что и исходный) и имеющий рандомизированное ( mktemp-стиль) имя.
  3. Новый файл тогда rename(2)d -d по сравнению с оригинальным; эта операция гарантирует, что каждый наблюдатель, пытающийся открыть файл, используя его имя, получит либо старое содержимое, либо новое.

Такие обновления рассматриваются inotify(7)как moved_toсобытия - поскольку файл «появляется» в каталоге.

kostix
источник
Ах, для некоторых файлов я думаю, что это делает это: я вижу различные IN_MOVED_FROMи IN_MOVED_TOсобытия. Тем не менее, я не вижу это происходит за .packи .idxфайлы
Михал Charemza
Файлы пакета могут быть огромными (несколько гигабайт, по крайней мере, до 2 ГБ, я считаю); их использование с использованием атомарных обновлений может быть запрещающим для пространства хранения, поэтому они могут обновляться с использованием другой стратегии.
kostix
2

Основываясь на этом принятом ответе, я предположил бы, что могут быть некоторые различия в событиях, основанных на используемом протоколе (то есть ssh или https).

Наблюдаете ли вы то же поведение при мониторинге клонирования из локальной файловой системы с помощью --no-hardlinksопции?

$ git clone git@github.com:user/repo.git
# set up watcher for new dir
$ git clone --no-hardlinks repo new-repo

Наблюдаемое вами поведение при запуске эксперимента на хостах Linux и Mac, вероятно, устраняет эту открытую проблему, являющуюся причиной https://github.com/docker/for-mac/issues/896, но добавляя просто incase.

dwrecked
источник
2

Есть еще одна возможность (от man inotify):

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

И хотя он git cloneможет генерировать большой поток событий, это может произойти.

Как этого избежать:

  1. Увеличьте буфер чтения, попробуйте fcntl (F_SETPIPE_SZ) (этот подход - предположение, я никогда не пробовал).
  2. Чтение событий в большой буфер в выделенном потоке, обработка событий в другом потоке.
Юрий Невиницин
источник
2

Может быть, вы сделали ту же ошибку, которую я сделал несколько лет назад. Я использовал inotify только дважды. В первый раз мой код просто работал. Позже у меня больше не было этого источника, и я начал заново, но на этот раз я пропустил события и не знал, почему.

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

donjuedo
источник