查询同步
文件系统监视器需要确保查询看到最新的视图。 Watchman 通过为每个查询创建一个唯一的cookie 来确保这一点。
背景
考虑一个目录树遍历来收集文件状态,例如 hg status
或 git status
所执行的遍历。 该遍历将与同时发生的任何操作竞争,这是不可能修复的。 但是,我们确实获得了一些较弱的保证
- 在遍历开始之前发生的每个文件操作都将被观察到。
- 在遍历开始之后发生的文件操作可能会被观察到,也可能不会被观察到。
对于 watchman,cookie 使我们能够提供类似的保证。 对于给定的 watchman 查询
- 在查询开始之前发生的每个文件操作都将被观察到。
- 在查询开始之后发生的文件操作可能会被观察到,也可能不会被观察到。
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 时的这种情况
- 发生事件 A,这会导致递归读取
.hg
- 发生事件 B,该事件触及文件
subdir/foo
- 在
.hg
中创建 cookie,导致事件 C - 从 OS 文件通知系统读取事件 A,但不读取事件 B 和 C
- 找到了 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_period
和 settle_timeout
。 两者都是整数毫秒,settle_period
指定在监视器被认为已赶上之前所需的静止期。
鸣谢
这个想法最初是由 Matt Mackall mpm@selenic.com 提出的。