跳到主要内容

查询同步

文件系统监视器需要确保查询看到最新的视图。 Watchman 通过为每个查询创建一个唯一的cookie 来确保这一点。

背景

考虑一个目录树遍历来收集文件状态,例如 hg statusgit status 所执行的遍历。 该遍历将与同时发生的任何操作竞争,这是不可能修复的。 但是,我们确实获得了一些较弱的保证

  1. 在遍历开始之前发生的每个文件操作都将被观察到。
  2. 在遍历开始之后发生的文件操作可能会被观察到,也可能不会被观察到。

对于 watchman,cookie 使我们能够提供类似的保证。 对于给定的 watchman 查询

  1. 在查询开始之前发生的每个文件操作都将被观察到。
  2. 在查询开始之后发生的文件操作可能会被观察到,也可能不会被观察到。

cookie 的工作原理

cookie 是在 watchman 观察的目录中创建的临时文件。 cookie 是在一个预期不会消失的目录中创建的。 显而易见的位置是根目录本身,但我们不希望 cookie 出现在 VCS 操作中。 因此,如果找到 VCS 目录(.git.hg.svn),则会在该目录中创建 cookie。

cookie 是在根目录被锁定时创建的,因此 watchman 在处理先前运行的事件时不会意外地找到 cookie。

创建 cookie 后,调用线程会在根目录锁保护的条件变量上等待。 这会导致锁被释放,并且根目录的通知线程现在可以像往常一样读取事件。

当通知线程发现它正在处理 cookie 时,它将向其各自的条件变量发出信号。 重要的是,这不会立即唤醒调用线程:由于通知线程仍然持有根锁,因此调用线程只有在通知线程释放锁后才能继续。

cookie 为我们带来了什么?

inotify 这样的文件监控系统通常提供排序保证:通知按其发生的顺序到达。 在创建 cookie 之前发生的任何事件都将出现在 cookie 的事件之前,这意味着它们将在查询得到解答之前被处理。

cookie 的工作效果如何?

Mercurial 测试套件已被证明是 watchman 的良好压力测试。 在实现 cookie 之前,如果从套件并行运行 16 个或更多测试,watchman 将开始落后并经常产生过时的答案。 Cookie 已经成功根除了这种情况。

即使尚未处理导致其出现的所有事件,watchman 是否可以找到 cookie?

考虑在 .hg 中创建 cookie 时的这种情况

  1. 发生事件 A,这会导致递归读取 .hg
  2. 发生事件 B,该事件触及文件 subdir/foo
  3. .hg 中创建 cookie,导致事件 C
  4. 从 OS 文件通知系统读取事件 A,但不读取事件 B 和 C
  5. 找到了 cookie,但从未读取 subdir/foo

在 Linux 上,为防止这种情况发生,watchman 仅在通过 OS 通知直接返回 cookie 时才认为已找到 cookie。 唯一的例外是在初始爬取或重新爬取期间,此时尚未监视 cookie 目录。

在其他平台上,这变得更加复杂,因为相应的监视系统仅告诉我们目录中创建了某些内容,而不是创建了什么。 这目前是一个未解决的问题。

限制:macOS FSEvents

在 macOS 上,Watchman 使用 FSEvents 来监视文件系统更改。 Watchman 结合使用了上述 cookie 文件和 FSEventStreamFlushSync,试图赶上之前的所有更改。

不幸的是,在高负载情况下(例如繁忙主机上的大型 git checkout),我们观察到来自 git checkout 的 FSEvents 可能在 cookie 文件通知和 FSEventStreamFlushSync 返回后收到。

事实证明,FSEvents 在这里不提供任何保证,并且在任何当前的 Apple 平台上都不支持依赖它进行查询同步。 他们建议的解决方法是使用 Endpoint Security 实现一个监视器。 尚未有人评估这种方法的可行性。

同时,您可以在查询上设置 settle_periodsettle_timeout。 两者都是整数毫秒,settle_period 指定在监视器被认为已赶上之前所需的静止期。

鸣谢

这个想法最初是由 Matt Mackall mpm@selenic.com 提出的。