【问题标题】:Missing inotify events (in .git directory)缺少 inotify 事件(在 .git 目录中)
【发布时间】:2020-01-22 16:59:06
【问题描述】:

我正在使用 inotify 事件监视文件的更改(碰巧,从 Python 调用 libc)。

对于git clone 期间的某些文件,我看到了一些奇怪的东西:我看到了IN_CREATE 事件,并且我通过ls 看到该文件有内容,但是,我从未看到IN_MODIFYIN_CLOSE_WRITE .这引起了我的问题,因为我想就文件回复IN_CLOSE_WRITE:特别是启动文件内容的上传。

行为异常的文件位于.git/objects/pack 目录中,它们以.pack.idx 结尾。 git 创建的其他文件具有更常规的 IN_CREATE -> IN_MODIFY -> IN_CLOSE_WRITE 链(我不关注 IN_OPEN 事件)。

这是在 MacOS 上的 docker 内部,但我在远程系统的 Linux 上的 docker 上看到了相同的证据,所以我怀疑 MacOS 方面不相关。如果观看和 git clonesame docker 容器中,我会看到这一点。

我的问题:

  • 为什么这些文件中缺少这些事件?

  • 可以做些什么呢?具体来说,我该如何响应对这些文件的写入完成?注意:理想情况下,我想在写作“完成”时做出回应,以避免不必要/(错误地)上传“未完成”的写作。


编辑:阅读https://developer.ibm.com/tutorials/l-inotify/ 看起来我所看到的与此一致

  • 一个单独的临时文件,名称类似于tmp_pack_hBV4Alz,正在创建、修改和关闭;
  • 一个链接被创建到这个文件,最终名称为.pack
  • tmp_pack_hBV4Alz名称被删除。

我认为我的问题是尝试使用 inotify 作为触发器来上传文件,然后减少到注意到 .pack 文件是指向另一个文件的硬链接,并在这种情况下上传?

【问题讨论】:

  • 答案可能在某处here...
  • @choroba 你可能是对的...我看到很多对 mmap 的引用,而 inotify 没有报告 mmap 对文件的访问权限
  • 顺便说一句,您要解决的原始问题是什么(使用 inotify)?是否存在一些更强大的解决方案来尝试猜测 Git 进程正在做什么/已经对存储库做了什么?
  • @kostix 这是github.com/uktrade/mobius3 的一部分,将用户的主文件夹从在 AWS Fargate 中运行 JupyterLab 或 RStudio 的容器同步到 S3,在这些主文件夹中可以有 .git 文件夹。我知道 inotify 解决方案永远不会“健壮-健壮”......但我希望它可以“足够健壮”。
  • @tink 看起来接受的答案是 Linux 内核上的补丁?我怀疑一般来说它会起作用,但在我的 Fargate 案例中,我没有那种控制权。 (而且我承认我有点害怕长期依赖修补内核的后果,即使我有这种能力......)

标签: linux git docker libc inotify


【解决方案1】:

在 Linux 4.19.95 上针对 git 2.24.1 单独回答您的问题:

  • 为什么这些文件中缺少这些事件?

您看不到IN_MODIFY/IN_CLOSE_WRITE 事件,因为git clone 将始终尝试对.git/objects 目录下的文件使用硬链接。通过网络或跨文件系统边界进行克隆时,这些事件将再次出现。

  • 可以做些什么呢?具体来说,我该如何响应对这些文件的写入完成?注意:理想情况下,我想在写作“完成”时做出回应,以避免不必要/(错误地)上传“未完成”的写作。

为了捕捉硬链接的修改,您必须为 inotify CREATE 事件设置一个处理程序,该事件跟踪并跟踪这些链接。请注意,简单的CREATE 也可能意味着创建了一个非空文件。然后,在IN_MODIFY/IN_CLOSE_WRITE 到任何文件,您也必须对所有链接文件触发相同的操作。显然,您还必须在 DELETE 事件中删除这种关系。

一种更简单、更可靠的方法可能是定期对所有文件进行哈希处理并检查文件内容是否已更改。


修正

在仔细检查git 源代码并运行gitstrace 后,我发现git 确实使用内存映射文件,但主要用于读取内容。 See the usage of xmmap which is always called with PROT_READ only.。因此,我之前的答案是 NOT 正确答案。尽管如此,出于提供信息的目的,我仍想将其保留在这里:

  • 您看不到IN_MODIFY 事件,因为packfile.c 使用mmap 进行文件访问,而inotify 不报告对mmaped 文件的修改。

    来自inotify manpage

    inotify API 不会报告由于 mmap(2)、msync(2) 和 munmap(2) 而可能发生的文件访问和修改。

【讨论】:

  • 我的更改检测机制依赖于IN_CLOSE_WRITE,我认为在关闭使用mmap 写入的文件时仍会触发该机制,因为该文件必须在写模式?
  • 我必须对此进行调查,但我怀疑内存映射文件根本不会触发任何 inotify 事件。大多数 inify 事件都与文件描述符的状态相关联,但是当您 mmap 文件时,事情可能会有点乱。例如,当您将文件映射到内存时,您仍然可以写入已关闭的文件描述符。
  • 从头开始,我刚刚测试了this example implementation,我确实得到了CLOSE_WRITE_CLOSE,即使我在最后删除了closemunmap。然后必须深入挖掘实际的 git 实现..
  • 嗯,我很难重现您的问题。在我对inotifywaitgit clone (2.24.1) 的测试中,我确实得到了OPEN -> CLOSE_NOWRITE,CLOSE 用于*.idx 文件。也许您忘记为CLOSE_NOWRITE,CLOSE 设置处理程序?注意:你会得到一个*NOWRITE*,因为所有的写入都是通过内存映射发生的。
  • 是的,有CLOSE_NOWRITE:问题是我没有看到IN_CLOSE_WRITE,我想响应文件“更改”以触发上传,但忽略文件“读取” .请注意,我实际上认为现在 mmap+inotify 限制有点像红鲱鱼。我认为问题在于 .pack/.idx 文件最初是作为指向另一个文件的硬链接创建的,因此仅触发 IN_CREATE (而 OPEN -> CLOSE_NOWRITE 稍后会在 git 实际读取文件)。
【解决方案2】:

我可能推测 G​​it 大部分时间都使用 atomic 文件更新,这些更新是这样完成的:

  1. 文件的内容被读入内存(并被修改)。
  2. 修改后的内容被写入一个单独的文件(通常与原始文件位于同一目录中,并具有随机 (mktemp-style) 名称。
  3. 然后新文件是rename(2)d -d 在原来的文件上;此操作可确保每个尝试使用其名称打开文件的观察者都将获得旧内容或新内容。

inotify(7) 将此类更新视为moved_to 事件——因为文件“重新出现”在目录中。

【讨论】:

  • 啊,对于某些文件,我认为它是这样做的:我看到了各种 IN_MOVED_FROMIN_MOVED_TO 事件。但是,我没有看到 .pack.idx 文件发生这种情况
  • 打包文件可能很大(几千兆字节,至少高达 2GiB,我相信);使用原子更新使用它们可能会占用存储空间,因此可能会使用其他策略对其进行更新。
【解决方案3】:

基于this accepted answer,我假设基于所使用的协议(即 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。

【讨论】:

    【解决方案4】:

    还有另一种可能性(来自 man inotify):

    请注意,事件队列可能会溢出。在这种情况下,事件是 丢失。健壮的应用程序应该处理丢失的可能性 优雅地举办活动。例如,可能需要重建零件 或所有应用程序缓存。 (一个简单但可能 昂贵,方法是关闭inotify文件描述符,为空 缓存,创建一个新的inotify文件描述符,然后重新创建 监视和缓存要监视的对象的条目。)

    虽然git clone 会产生大量事件流,但这种情况可能会发生。

    如何避免:

    1. 增加读取缓冲区,试试 fcntl(F_SETPIPE_SZ) (这种方法是猜测,我从未尝试过)。
    2. 在专用线程中将事件读入大缓冲区,在另一个线程中处理事件。

    【讨论】:

      【解决方案5】:

      也许你犯了我多年前犯的同样错误。我只使用了两次inotify。第一次,我的代码很简单。后来,我不再有那个来源并重新开始,但这一次,我错过了事件,不知道为什么。

      事实证明,当我在阅读一个事件时,我实际上是在阅读一小部分事件。我解析了我所期望的,认为就是这样,仅此而已。最终,我发现接收到的数据还有更多,当我添加一些代码来解析从一次读取中接收到的所有事件时,不再丢失任何事件。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-05-28
        • 2018-10-25
        • 1970-01-01
        • 2012-12-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多